Skip to content

Commit 6876fc8

Browse files
authored
feat(UI): complete Mr Reviewer feature (#1509)
* feat(UI): update reviewer hooks * feat(UI): complete Mr Reviewer feature * feat(UI): complete Mr Reviewer feature
1 parent 3b44eef commit 6876fc8

File tree

15 files changed

+564
-151
lines changed

15 files changed

+564
-151
lines changed

moon/apps/web/components/Issues/IssueDetailPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { usePostIssueLabels } from '@/hooks/usePostIssueLabels'
2525
import { useUploadHelpers } from '@/hooks/useUploadHelpers'
2626
import { apiErrorToast } from '@/utils/apiErrorToast'
2727
import { trimHtml } from '@/utils/trimHtml'
28+
import { CommonDetailData } from '@/utils/types'
2829

2930
import { MemberAvatar } from '../MemberAvatar'
3031
import TimelineItems from '../MrView/TimelineItems'
@@ -316,7 +317,7 @@ export default function IssueDetailPage({ link }: { link: string }) {
316317
<LoadingSpinner />
317318
</div>
318319
) : (
319-
<TimelineItems detail={issueDetail} id={link} type='issue' editorRef={editorRef} />
320+
<TimelineItems detail={issueDetail as CommonDetailData} id={link} type='issue' editorRef={editorRef} />
320321
)}
321322

322323
{info && info.status === 'open' && (

moon/apps/web/components/MrBox/ChecksSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ export function ChecksSection({ checks, onStatusChange, additionalChecks }: Chec
201201
/>
202202
</Collapsible.Trigger>
203203

204-
{/* CheckList 部分 */ }
204+
{/* CheckList & AdditionalChecks 部分 */ }
205205
<Collapsible.Content>
206206
<CheckList groupedChecks={ groupedChecks }/>
207207
<AdditionalChecksSection additionalChecks={ additionalChecks || [] }/>

moon/apps/web/components/MrBox/MergeBox.tsx

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useCallback } from 'react';
1+
import React, { useState, useCallback, useMemo } from 'react';
22
import { useMergeChecks } from './hooks/useMergeChecks';
33
import { ReviewerSection } from './ReviewerSection';
44
import { ChecksSection } from './ChecksSection';
@@ -7,16 +7,39 @@ import { FeedMergedIcon } from "@primer/octicons-react";
77
import { usePostMrMerge } from "@/hooks/usePostMrMerge";
88
import { useGetMergeBox } from "@/components/MrBox/hooks/useGetMergeBox";
99
import { LoadingSpinner } from "@gitmono/ui";
10-
11-
const REQUIRED_REVIEWERS = 2; // 假设需要2个 reviewer
10+
import { usePostMrReviewerApprove } from "@/hooks/usePostMrReviewerApprove";
11+
import { useRouter } from "next/router";
12+
import { useGetMrReviewers } from "@/hooks/useGetMrReviewers";
13+
import { useQueryClient } from '@tanstack/react-query';
14+
import { legacyApiClient } from '@/utils/queryClient';
15+
import { useGetCurrentUser } from "@/hooks/useGetCurrentUser";
1216

1317
export function MergeBox({ prId }: { prId: string }) {
1418
const { checks, refresh } = useMergeChecks(prId);
15-
const [isReviewerApproved, setIsReviewerApproved] = useState(false);
1619
const [hasCheckFailures, setHasCheckFailures] = useState(true);
1720
const { mutate: approveMr, isPending: mrMergeIsPending } = usePostMrMerge(prId)
21+
const { mutate: reviewApprove } = usePostMrReviewerApprove()
22+
const queryClient = useQueryClient();
23+
24+
const route = useRouter();
25+
const { link } = route.query;
26+
const id = typeof link === 'string'? link : '';
27+
const { reviewers, isLoading: isReviewerLoading } = useGetMrReviewers(id)
28+
29+
const required: number = useMemo(() => reviewers.length, [reviewers]);
30+
const actual: number = useMemo(() => reviewers.filter(i => i.approved).length, [reviewers]);
31+
const isAllReviewerApproved: boolean = useMemo(() => actual >= required, [actual, required]);
32+
33+
let isNowUserApprove: boolean | undefined = undefined;
34+
const { data } = useGetCurrentUser()
35+
const find_user = reviewers.find(i => i.username === data?.username)
36+
37+
if(find_user) {
38+
isNowUserApprove = find_user.approved
39+
}
40+
41+
const { mergeBoxData, isLoading: isAdditionLoading } = useGetMergeBox(prId)
1842

19-
const { mergeBoxData, isLoading } = useGetMergeBox(prId)
2043

2144
// 定义最终的合并处理函数
2245
const handleMerge = useCallback(async () => {
@@ -38,30 +61,47 @@ export function MergeBox({ prId }: { prId: string }) {
3861
}
3962
}, [approveMr, refresh]);
4063

64+
const handleApprove = useCallback(async () => {
65+
reviewApprove({
66+
link: id,
67+
data: {
68+
approved: true
69+
}
70+
}, {
71+
onSuccess: () => {
72+
queryClient.invalidateQueries({
73+
queryKey: legacyApiClient.v1.getApiMrReviewers().requestKey(id)
74+
});
75+
}
76+
});
77+
}, [reviewApprove, id, queryClient]);
78+
4179
const additionalChecks = mergeBoxData?.merge_requirements?.conditions ?? []
4280

4381
return (
4482
<div className="flex">
4583
<FeedMergedIcon size={ 24 } className='text-gray-500 ml-1'/>
46-
{ isLoading? (
84+
{ isReviewerLoading && isAdditionLoading? (
4785
<div className='flex h-[400px] items-center justify-center'>
4886
<LoadingSpinner/>
4987
</div>
5088
) : (
5189
<div className="border rounded-lg bg-white divide-y ml-3 w-full">
5290
<ReviewerSection
53-
required={ REQUIRED_REVIEWERS }
54-
onStatusChange={ setIsReviewerApproved }
91+
required={required}
92+
actual={actual}
5593
/>
5694
<ChecksSection
5795
checks={ checks }
5896
onStatusChange={ setHasCheckFailures }
5997
additionalChecks={ additionalChecks }
6098
/>
6199
<MergeSection
62-
isReviewerApproved={ isReviewerApproved }
100+
isNowUserApprove={isNowUserApprove}
101+
isAllReviewerApproved={ isAllReviewerApproved }
63102
hasCheckFailures={ hasCheckFailures }
64103
onMerge={ handleMerge }
104+
onApprove={ handleApprove }
65105
isMerging={ mrMergeIsPending }
66106
/>
67107
</div>

moon/apps/web/components/MrBox/MergeSection.tsx

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ import { AlertIcon, WarningTriangleIcon, CheckCircleIcon, LoadingSpinner } from
22
import React from "react";
33

44
interface MergeSectionProps {
5-
isReviewerApproved: boolean;
5+
isNowUserApprove?: boolean;
6+
isAllReviewerApproved: boolean;
67
hasCheckFailures: boolean;
78
onMerge: () => Promise<void>;
9+
onApprove: () => void;
810
isMerging: boolean;
911
}
1012

11-
export function MergeSection({ isReviewerApproved, hasCheckFailures, onMerge, isMerging }: MergeSectionProps) {
13+
export function MergeSection({ isAllReviewerApproved, hasCheckFailures, isNowUserApprove, onMerge, onApprove, isMerging }: MergeSectionProps) {
1214
let statusNode: React.ReactNode;
13-
const isMergeable = isReviewerApproved && !hasCheckFailures;
15+
const isMergeable = isAllReviewerApproved && !hasCheckFailures;
1416

15-
if (!isReviewerApproved) {
17+
if (!isAllReviewerApproved) {
1618
statusNode = (
1719
<div className="flex items-center text-yellow-700">
1820
<WarningTriangleIcon className="h-5 w-5 mr-3" />
@@ -38,15 +40,28 @@ export function MergeSection({ isReviewerApproved, hasCheckFailures, onMerge, is
3840
return (
3941
<div className="p-3">
4042
{statusNode}
41-
<button
42-
onClick={onMerge}
43-
disabled={!isMergeable}
44-
className="w-full mt-3 px-4 py-2 font-bold text-white bg-green-600 rounded-md
45-
hover:bg-green-700
43+
<div className="flex items-center justify-center space-x-4">
44+
<button
45+
onClick={onApprove}
46+
disabled={isNowUserApprove === undefined || isNowUserApprove}
47+
className="w-full mt-3 px-4 py-2 font-bold text-white bg-green-600 rounded-md
48+
hover:bg-green-800
49+
duration-500
4650
disabled:bg-gray-400 disabled:cursor-not-allowed"
47-
>
48-
{isMerging ? <LoadingSpinner/> : "Confirm Merge"}
49-
</button>
51+
>
52+
{"Approve"}
53+
</button>
54+
<button
55+
onClick={onMerge}
56+
disabled={!isMergeable}
57+
className="w-full mt-3 px-4 py-2 font-bold text-white bg-green-600 rounded-md
58+
hover:bg-green-800
59+
duration-500
60+
disabled:bg-gray-400 disabled:cursor-not-allowed"
61+
>
62+
{isMerging ? <LoadingSpinner/> : "Confirm Merge"}
63+
</button>
64+
</div>
5065
</div>
5166
);
5267
}

moon/apps/web/components/MrBox/ReviewerSection.tsx

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,17 @@
1-
import { useState, useEffect } from 'react';
21
import { CheckCircleIcon, AlertIcon } from "@gitmono/ui";
32

43
interface ReviewerSectionProps {
54
required: number;
6-
onStatusChange: (isApproved: boolean) => void;
5+
actual: number;
76
}
87

9-
export function ReviewerSection({ required, onStatusChange }: ReviewerSectionProps) {
10-
// 实际的 review 人数,暂时用 state 模拟,未来应由 API 获取
11-
const [actual, _setActual] = useState(1);
8+
export function ReviewerSection({ required, actual }: ReviewerSectionProps) {
129
const isApproved = actual >= required;
1310

14-
useEffect(() => {
15-
// TODO: 调用 API 获取实际 review 人数并更新 setActual
16-
// fetch('/api/.../reviewers').then(res => res.json()).then(data => setActual(data.count));
17-
}, []);
18-
19-
useEffect(() => {
20-
onStatusChange(isApproved);
21-
}, [isApproved, onStatusChange]);
22-
2311
if (isApproved) {
2412
return (
2513
<div className="flex items-center p-3 text-green-700">
26-
<CheckCircleIcon className="h-5 w-5 mr-3" />
14+
<CheckCircleIcon className="h-5 w-5 mr-3"/>
2715
<div>
2816
<span className="font-semibold">All required reviewers have approved</span>
2917
</div>
@@ -33,11 +21,11 @@ export function ReviewerSection({ required, onStatusChange }: ReviewerSectionPro
3321

3422
return (
3523
<div className="flex items-center p-3 text-gray-800">
36-
<AlertIcon className="h-5 w-5 mr-3 text-yellow-600" />
24+
<AlertIcon className="h-5 w-5 mr-3 text-yellow-600"/>
3725
<div>
3826
<div className="font-semibold">Review required</div>
3927
<div className="ml-auto text-sm text-gray-500">
40-
{`At least ${required} reviewer${required > 1 ? 's' : ''} required with write access.`}
28+
{ `At least ${ required } reviewer${ required > 1? 's' : '' } required with write access, now has ${ actual }` }
4129
</div>
4230
</div>
4331
</div>

0 commit comments

Comments
 (0)