66 >
77 <div class =" flex flex-col" >
88 <div class =" flex justify-center text-left" >
9- <div v-if =" data " class =" flex flex-col gap-4" >
9+ <div v-if =" pullRequests " class =" flex flex-col gap-4" >
1010 <div
11- v-for =" pr in data "
11+ v-for =" pr in pullRequests "
1212 :key =" pr.id"
1313 :class =" {
14- 'flex flex-wrap gap-y-0.5 ': true,
14+ 'flex justify-between gap-16 ': true,
1515 'transition-opacity duration-100 ease-in-out opacity-50 hover:opacity-100':
1616 pr.draft,
1717 }"
1818 >
19- <div class =" flex items-center gap-2 grow-1" >
20- <a
21- :href =" pr.html_url"
22- target =" _blank"
23- :class =" {
24- 'font-bold hover:text-sky-400': true,
25- 'text-muted-color': pr.draft,
26- }"
27- >
28- {{ pr.title }}
29- </a >
30- <Tag
31- v-for =" label in pr.labels"
32- :value =" label.name"
33- :key =" `${label.id}`"
34- v-tooltip =" label.description"
35- :pt:label:class =" 'text-2xs'"
36- :dt =" {
37- padding: '0.15rem 0.5rem',
38- }"
39- :style =" {
40- backgroundColor: `#${label.color}`,
41- color: getFrontColor(`#${label.color}`),
42- }"
43- ></Tag >
19+ <div class =" flex flex-col" >
20+ <div class =" flex items-center gap-2 grow-1" >
21+ <a
22+ :href =" pr.html_url"
23+ target =" _blank"
24+ :class =" {
25+ 'font-bold hover:text-sky-400': true,
26+ 'text-muted-color': pr.draft,
27+ }"
28+ >
29+ {{ pr.title }}
30+ </a >
31+ <Tag
32+ v-for =" label in pr.labels"
33+ :value =" label.name"
34+ :key =" `${label.id}`"
35+ v-tooltip =" label.description"
36+ :pt:label:class =" 'text-2xs'"
37+ :dt =" {
38+ padding: '0.15rem 0.5rem',
39+ }"
40+ :style =" {
41+ backgroundColor: `#${label.color}`,
42+ color: getFrontColor(`#${label.color}`),
43+ }"
44+ ></Tag >
45+ </div >
46+
47+ <div class =" text-muted-color text-xs w-full" >
48+ <span class =" text-muted-color-emphasis" >#{{ pr.number }}</span >
49+ opened by <UserTag :user =" pr.user" />
50+ <span v-if =" pr.base.ref !== 'master'" class =" ml-2" >
51+ &rArr ;
52+ <span class =" ml-2 text-primary-emphasis" >{{
53+ pr.base.ref
54+ }}</span >
55+ </span >
56+ </div >
57+ </div >
58+ <div
59+ v-if =" pr.review && pr.review.changes_requested"
60+ class =" text-red-400 font-bold"
61+ >
62+ Changes requested.
63+ </div >
64+ <div v-else-if =" pr.review && pr.review.approved" class =" flex flex-col" >
65+ <div class =" text-green-500 text-right" >Approved</div >
66+ <div class =" text-xs" >
67+ by
68+ <UserTag
69+ v-for =" (user, idx) in pr.review.by"
70+ :user =" user"
71+ :key =" `u${pr.id}-${idx}`"
72+ />
73+ </div >
4474 </div >
4575 <div
46- v-if =" !pr.draft"
76+ v-else- if =" !pr.draft"
4777 :class =" {
4878 'shrink-1': true,
4979 'text-orange-400': !pr.draft && isMoreThanXdays(pr.created_at, 7),
5888 <span v-else-if="isMoreThanXdays(pr.created_at, 2)">🙂</span>
5989 <span v-else>😄</span> -->
6090 </div >
61- <div v-if =" pr.draft" class =" text-muted-color" >
91+ <div v-else- if =" pr.draft" class =" text-muted-color" >
6292 {{ computeHowLongAgo(pr.created_at) }}
6393 <!-- <span>🫣</span> -->
6494 </div >
65- <div class =" text-muted-color text-xs w-full" >
66- <span class =" text-muted-color-emphasis" >#{{ pr.number }}</span > opened
67- by
68- <a :href =" pr.user.html_url" class =" text-muted-color-emphasis"
69- ><img :src =" pr.user.avatar_url" class =" rounded-full h-4 inline" />
70- {{ pr.user.login }}</a
71- >
72- <span v-if =" pr.base.ref !== 'master'" class =" ml-2" >
73- &rArr ;
74- <span class =" ml-2 text-primary-emphasis" >{{ pr.base.ref }}</span >
75- </span >
76- </div >
7795 </div >
7896 </div >
7997 <div v-else >
@@ -90,63 +108,18 @@ import { ref } from 'vue';
90108import { onMounted } from ' vue' ;
91109import Tag from ' primevue/tag' ;
92110import { Panel } from ' primevue' ;
111+ import {
112+ type PullRequest ,
113+ type PullRequestReview ,
114+ type ReviewStatus ,
115+ type User ,
116+ APPROVED ,
117+ CHANGES_REQUESTED ,
118+ CONTRIBUTOR ,
119+ } from ' ./ResponsesTypes.ts' ;
120+ import UserTag from ' ./components/UserTag.vue' ;
93121
94- type PullRequest = {
95- active_lock_reason: unknown ;
96- assignee: unknown ;
97- assignees: string [];
98- author_association: string ;
99- auto_merge: null ;
100- body: string ;
101- closed_at: null ;
102- comments_url: string ;
103- commits_url: string ;
104- created_at: string ;
105- diff_url: string ;
106- draft: boolean ;
107- html_url: string ;
108- id: number ;
109- issue_url: string ;
110- labels: { color: string ; id: number ; description: string ; name: string }[];
111- locked: boolean ;
112- merge_commit_sha: string ;
113- merged_at: null ;
114- milestone: null ;
115- node_id: string ;
116- number: number ;
117- patch_url: string ;
118- requested_reviewers: [];
119- review_comment_url: string ;
120- review_comments_url: string ;
121- state: ' open' ;
122- statuses_url: string ;
123- title: string ;
124- updated_at: string ;
125- url: string ;
126- user: {
127- login: string ;
128- id: number ;
129- node_id: string ;
130- avatar_url: string ;
131- gravatar_id: string ;
132- url: string ;
133- html_url: string ;
134- followers_url: string ;
135- following_url: string ;
136- gists_url: string ;
137- starred_url: string ;
138- subscriptions_url: string ;
139- organizations_url: string ;
140- repos_url: string ;
141- events_url: string ;
142- received_events_url: string ;
143- type: string ;
144- site_admin: boolean ;
145- };
146- base: { ref: string };
147- };
148-
149- const data = ref <PullRequest [] | undefined >(undefined );
122+ const pullRequests = ref <(PullRequest & ReviewStatus )[] | undefined >(undefined );
150123const octokit = new Octokit ();
151124
152125function colorIsDarkSimple(bgColor : string ): boolean {
@@ -188,24 +161,89 @@ function isMoreThanXdays(dateString: string, x: number): boolean {
188161 return days > x ;
189162}
190163
191- async function getPrs() {
192- octokit .rest .pulls
164+ async function getPrs(): Promise < void > {
165+ return octokit .rest .pulls
193166 .list ({
194167 owner: ' LycheeOrg' ,
195168 repo: ' Lychee' ,
196169 })
197170 .then ((response ) => {
198- data .value = response .data as unknown as PullRequest [];
199- console .log (' Pull requests fetched successfully:' , data .value );
171+ pullRequests .value = response .data as unknown as PullRequest [];
200172 })
201173 .catch ((error ) => {
202174 console .error (' Error fetching pull requests:' , error );
203175 });
204- // const response = await fetch('https://api.github.com/repos/LycheeOrg/Lychee/pulls');
205- // data.value = await response.json();
206176}
207177
208- onMounted (() => {
209- getPrs ();
178+ async function getStatuses() {
179+ if (! pullRequests .value || pullRequests .value .length === 0 ) {
180+ console .warn (' No pull requests available to fetch statuses for.' );
181+ return ;
182+ }
183+
184+ pullRequests .value .forEach (async (pr , idx ) => {
185+ await octokit .rest .pulls
186+ .listReviews ({
187+ owner: ' LycheeOrg' ,
188+ repo: ' Lychee' ,
189+ pull_number: pr .number , // Use the pull request number from the fetched PRs
190+ })
191+ .then ((response ) => {
192+ console .log (
193+ ` Pull request reviews for PR #${pr .number } fetched successfully: ` ,
194+ response .data ,
195+ );
196+ if (! pullRequests .value ) {
197+ console .warn (' pullRequests.value is undefined, cannot update review status.' );
198+ return ;
199+ }
200+
201+ pullRequests .value [idx ].review = extractStatusForPr (
202+ response .data as PullRequestReview [],
203+ ).review ;
204+ })
205+ .catch ((error ) => {
206+ console .error (` Error fetching pull request reviews for PR #${pr .number }: ` , error );
207+ });
208+ });
209+ }
210+
211+ function extractStatusForPr(reviews : PullRequestReview []): ReviewStatus {
212+ // Loop through the reviews.
213+ // Ignore all the reviews that are not from the contributors.
214+ const statuses = reviews .reduce (
215+ (acc , review ) => {
216+ if (
217+ (review .state === APPROVED || review .state === CHANGES_REQUESTED ) &&
218+ review .author_association === CONTRIBUTOR
219+ ) {
220+ acc [review .user .login ] = { status: review .state , user: review .user };
221+ }
222+ return acc ;
223+ },
224+ {} as Record <string , { status: string ; user: User }>,
225+ );
226+
227+ const result: ReviewStatus = {
228+ review: {
229+ approved: false ,
230+ changes_requested: false ,
231+ by: [],
232+ },
233+ };
234+ Object .entries (statuses ).forEach (([_no , review ]) => {
235+ if (review .status === APPROVED ) {
236+ result .review ! .approved = true ;
237+ } else if (status === CHANGES_REQUESTED ) {
238+ result .review ! .changes_requested = true ;
239+ }
240+ result .review ! .by .push (review .user );
241+ });
242+ return result ;
243+ }
244+
245+ onMounted (async () => {
246+ await getPrs ();
247+ getStatuses ();
210248});
211249 </script >
0 commit comments