Skip to content

Commit 1467952

Browse files
committed
improved grouping
1 parent 2e38f97 commit 1467952

File tree

4 files changed

+260
-133
lines changed

4 files changed

+260
-133
lines changed

src/App.vue

Lines changed: 43 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -2,101 +2,30 @@
22
<Panel
33
class="bg-transparent border-none"
44
header="Lychee Pull Requests"
5-
:pt:header:class="'justify-center text-3xl font-bold mb-8'"
5+
:pt:header:class="'justify-center text-4xl font-bold mb-8'"
66
>
7-
<div class="flex flex-col">
8-
<div class="flex justify-center text-left">
9-
<div v-if="pullRequests" class="flex flex-col gap-2">
10-
<div
11-
v-for="pr in pullRequests"
12-
:key="pr.id"
13-
:class="{
14-
'flex justify-between gap-16': true,
15-
'transition-opacity duration-100 ease-in-out opacity-50 hover:opacity-100':
16-
pr.draft,
17-
}"
18-
>
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>
7+
<div class="flex flex-col max-w-4xl mx-auto gap-4 text-left">
8+
<template v-if="pullRequests">
9+
<template v-if="pullRequests.length === 1">
10+
<PrList v-if="pullRequests" :pull-requests="pullRequests[0].data" />
11+
</template>
12+
<template v-else>
13+
<div v-for="(group, idx) in pullRequests" :key="idx" class="group">
5814
<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>
74-
</div>
75-
<div
76-
v-else-if="!pr.draft"
7715
:class="{
78-
'shrink-1': true,
79-
'text-orange-400': !pr.draft && isMoreThanXdays(pr.created_at, 7),
80-
'text-red-400': !pr.draft && isMoreThanXdays(pr.created_at, 14),
81-
'': pr.draft,
16+
'opacity-50 group-hover:opacity-100 transition-opacity duration-100 ease-in-out':
17+
allDrafts(group.data),
18+
'mb-4 text-xl font-bold text-primary-emphasis border-b border-dashed border-surface-400': true,
8219
}"
8320
>
84-
{{ computeHowLongAgo(pr.created_at) }}
85-
<!-- <span v-if="isMoreThanXdays(pr.created_at, 30)">😭</span>
86-
<span v-else-if="isMoreThanXdays(pr.created_at, 14)">😢</span>
87-
<span v-else-if="isMoreThanXdays(pr.created_at, 7)">🥹</span>
88-
<span v-else-if="isMoreThanXdays(pr.created_at, 2)">🙂</span>
89-
<span v-else>😄</span> -->
90-
</div>
91-
<div v-else-if="pr.draft" class="text-muted-color">
92-
{{ computeHowLongAgo(pr.created_at) }}
93-
<!-- <span>🫣</span> -->
21+
{{ group.header }}
9422
</div>
23+
<PrList :pull-requests="group.data" />
9524
</div>
96-
</div>
97-
<div v-else>
98-
<p>Loading...</p>
99-
</div>
25+
</template>
26+
</template>
27+
<div v-else>
28+
<p class="text-center text-primary-emphasis">Loading...</p>
10029
</div>
10130
</div>
10231
</Panel>
@@ -106,7 +35,6 @@
10635
import { Octokit } from 'octokit';
10736
import { ref } from 'vue';
10837
import { onMounted } from 'vue';
109-
import Tag from 'primevue/tag';
11038
import { Panel } from 'primevue';
11139
import {
11240
type PullRequest,
@@ -117,48 +45,24 @@ import {
11745
CHANGES_REQUESTED,
11846
CONTRIBUTOR,
11947
} from './ResponsesTypes.ts';
120-
import UserTag from './components/UserTag.vue';
48+
import { useSplitter } from './composables/splitter';
49+
import { computed } from 'vue';
50+
import PrList from './components/PrList.vue';
12151
122-
const pullRequests = ref<(PullRequest & ReviewStatus)[] | undefined>(undefined);
52+
const { spliter } = useSplitter();
53+
const pullRequestsData = ref<(PullRequest & ReviewStatus)[] | undefined>(undefined);
54+
const pullRequests = computed(() => {
55+
if (!pullRequestsData.value) return undefined;
56+
return spliter(pullRequestsData.value, prToGroup, prToGroup);
57+
});
12358
const octokit = new Octokit();
12459
125-
function colorIsDarkSimple(bgColor: string): boolean {
126-
// Simple check for dark color based on luminance
127-
const color = bgColor.charAt(0) === '#' ? bgColor.substring(1, 7) : bgColor;
128-
const r = parseInt(color.substring(0, 2), 16); // hexToR
129-
const g = parseInt(color.substring(2, 4), 16); // hexToG
130-
const b = parseInt(color.substring(4, 6), 16); // hexToB
131-
return r * 0.299 + g * 0.587 + b * 0.114 <= 186;
132-
}
133-
134-
function getFrontColor(bgColor: string): string {
135-
if (colorIsDarkSimple(bgColor)) {
136-
return '#ffffff'; // white for dark backgrounds
60+
function prToGroup(pr: PullRequest): string {
61+
if (!pr.head.ref.includes('/')) {
62+
return 'standalone';
13763
}
138-
return '#000000'; // black for light backgrounds
139-
}
140-
141-
function computeHowLongAgo(dateString: string): string {
142-
const date = new Date(dateString);
143-
const now = new Date();
144-
const diff = now.getTime() - date.getTime();
145-
const seconds = Math.floor(diff / 1000);
146-
const minutes = Math.floor(seconds / 60);
147-
const hours = Math.floor(minutes / 60);
148-
const days = Math.floor(hours / 24);
149-
150-
if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
151-
if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
152-
if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
153-
return `${seconds} second${seconds > 1 ? 's' : ''} ago`;
154-
}
155-
156-
function isMoreThanXdays(dateString: string, x: number): boolean {
157-
const date = new Date(dateString);
158-
const now = new Date();
159-
const diff = now.getTime() - date.getTime();
160-
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
161-
return days > x;
64+
// select the part before the first dash
65+
return pr.head.ref.split('/')[0] || 'standalone';
16266
}
16367
16468
async function getPrs(): Promise<void> {
@@ -168,20 +72,20 @@ async function getPrs(): Promise<void> {
16872
repo: 'Lychee',
16973
})
17074
.then((response) => {
171-
pullRequests.value = response.data as unknown as PullRequest[];
75+
pullRequestsData.value = response.data as unknown as PullRequest[];
17276
})
17377
.catch((error) => {
17478
console.error('Error fetching pull requests:', error);
17579
});
17680
}
17781
17882
async function getStatuses() {
179-
if (!pullRequests.value || pullRequests.value.length === 0) {
83+
if (!pullRequestsData.value || pullRequestsData.value.length === 0) {
18084
console.warn('No pull requests available to fetch statuses for.');
18185
return;
18286
}
18387
184-
pullRequests.value.forEach(async (pr, idx) => {
88+
pullRequestsData.value.forEach(async (pr, idx) => {
18589
await octokit.rest.pulls
18690
.listReviews({
18791
owner: 'LycheeOrg',
@@ -193,12 +97,14 @@ async function getStatuses() {
19397
`Pull request reviews for PR #${pr.number} fetched successfully:`,
19498
response.data,
19599
);
196-
if (!pullRequests.value) {
197-
console.warn('pullRequests.value is undefined, cannot update review status.');
100+
if (!pullRequestsData.value) {
101+
console.warn(
102+
'pullRequestsData.value is undefined, cannot update review status.',
103+
);
198104
return;
199105
}
200106
201-
pullRequests.value[idx].review = extractStatusForPr(
107+
pullRequestsData.value[idx].review = extractStatusForPr(
202108
response.data as PullRequestReview[],
203109
).review;
204110
})
@@ -208,6 +114,10 @@ async function getStatuses() {
208114
});
209115
}
210116
117+
function allDrafts(prs: PullRequest[]): boolean {
118+
return prs.every((pr) => pr.draft);
119+
}
120+
211121
function extractStatusForPr(reviews: PullRequestReview[]): ReviewStatus {
212122
// Loop through the reviews.
213123
// Ignore all the reviews that are not from the contributors.

src/ResponsesTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export type PullRequest = {
3838
url: string;
3939
user: User;
4040
base: { ref: string };
41+
head: { ref: string };
4142
};
4243

4344
export const CONTRIBUTOR = 'CONTRIBUTOR';

src/components/PrList.vue

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<template>
2+
<div v-if="props.pullRequests" class="flex flex-col gap-2">
3+
<div
4+
v-for="pr in props.pullRequests"
5+
:key="pr.id"
6+
:class="{
7+
'flex justify-between gap-16': true,
8+
'transition-opacity duration-100 ease-in-out opacity-50 hover:opacity-100':
9+
pr.draft,
10+
}"
11+
>
12+
<div class="flex flex-col">
13+
<div class="flex items-center gap-2 grow-1">
14+
<a
15+
:href="pr.html_url"
16+
target="_blank"
17+
:class="{
18+
'font-bold hover:text-sky-400': true,
19+
'text-muted-color': pr.draft,
20+
}"
21+
>
22+
{{ pr.title }}
23+
</a>
24+
<Tag
25+
v-for="label in pr.labels"
26+
:value="label.name"
27+
:key="`${label.id}`"
28+
v-tooltip="label.description"
29+
:pt:label:class="'text-2xs'"
30+
:dt="{
31+
padding: '0.15rem 0.5rem',
32+
}"
33+
:style="{
34+
backgroundColor: `#${label.color}`,
35+
color: getFrontColor(`#${label.color}`),
36+
}"
37+
></Tag>
38+
</div>
39+
40+
<div class="text-muted-color text-xs w-full">
41+
<span class="text-muted-color-emphasis">#{{ pr.number }}</span>
42+
opened by <UserTag :user="pr.user" />
43+
<span v-if="pr.base.ref !== 'master'" class="ml-2">
44+
&rArr;
45+
<span class="ml-2 text-primary-emphasis">{{ pr.base.ref }}</span>
46+
</span>
47+
</div>
48+
</div>
49+
<div v-if="pr.review && pr.review.changes_requested" class="text-red-400 font-bold">
50+
Changes requested.
51+
</div>
52+
<div v-else-if="pr.review && pr.review.approved" class="flex flex-col">
53+
<div class="text-green-500 text-right">Approved</div>
54+
<div class="text-xs">
55+
by
56+
<UserTag
57+
v-for="(user, idx) in pr.review.by"
58+
:user="user"
59+
:key="`u${pr.id}-${idx}`"
60+
/>
61+
</div>
62+
</div>
63+
<div
64+
v-else-if="!pr.draft"
65+
:class="{
66+
'shrink-1': true,
67+
'text-orange-400': !pr.draft && isMoreThanXdays(pr.created_at, 7),
68+
'text-red-400': !pr.draft && isMoreThanXdays(pr.created_at, 14),
69+
'': pr.draft,
70+
}"
71+
>
72+
{{ computeHowLongAgo(pr.created_at) }}
73+
<!-- <span v-if="isMoreThanXdays(pr.created_at, 30)">😭</span>
74+
<span v-else-if="isMoreThanXdays(pr.created_at, 14)">😢</span>
75+
<span v-else-if="isMoreThanXdays(pr.created_at, 7)">🥹</span>
76+
<span v-else-if="isMoreThanXdays(pr.created_at, 2)">🙂</span>
77+
<span v-else>😄</span> -->
78+
</div>
79+
<div v-else-if="pr.draft" class="text-muted-color">
80+
{{ computeHowLongAgo(pr.created_at) }}
81+
<!-- <span>🫣</span> -->
82+
</div>
83+
</div>
84+
</div>
85+
</template>
86+
<script setup lang="ts">
87+
import { Tag } from 'primevue';
88+
import UserTag from './UserTag.vue';
89+
import type { PullRequest, ReviewStatus } from '@/ResponsesTypes';
90+
91+
const props = defineProps<{
92+
pullRequests: (PullRequest & ReviewStatus)[];
93+
}>();
94+
95+
function colorIsDarkSimple(bgColor: string): boolean {
96+
// Simple check for dark color based on luminance
97+
const color = bgColor.charAt(0) === '#' ? bgColor.substring(1, 7) : bgColor;
98+
const r = parseInt(color.substring(0, 2), 16); // hexToR
99+
const g = parseInt(color.substring(2, 4), 16); // hexToG
100+
const b = parseInt(color.substring(4, 6), 16); // hexToB
101+
return r * 0.299 + g * 0.587 + b * 0.114 <= 186;
102+
}
103+
104+
function getFrontColor(bgColor: string): string {
105+
if (colorIsDarkSimple(bgColor)) {
106+
return '#ffffff'; // white for dark backgrounds
107+
}
108+
return '#000000'; // black for light backgrounds
109+
}
110+
111+
function computeHowLongAgo(dateString: string): string {
112+
const date = new Date(dateString);
113+
const now = new Date();
114+
const diff = now.getTime() - date.getTime();
115+
const seconds = Math.floor(diff / 1000);
116+
const minutes = Math.floor(seconds / 60);
117+
const hours = Math.floor(minutes / 60);
118+
const days = Math.floor(hours / 24);
119+
120+
if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`;
121+
if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`;
122+
if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`;
123+
return `${seconds} second${seconds > 1 ? 's' : ''} ago`;
124+
}
125+
126+
function isMoreThanXdays(dateString: string, x: number): boolean {
127+
const date = new Date(dateString);
128+
const now = new Date();
129+
const diff = now.getTime() - date.getTime();
130+
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
131+
return days > x;
132+
}
133+
</script>

0 commit comments

Comments
 (0)