Skip to content

Commit 10d0f4c

Browse files
committed
feat: add mr detail page logic
1 parent a412132 commit 10d0f4c

File tree

7 files changed

+279
-19
lines changed

7 files changed

+279
-19
lines changed

src/init.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,21 @@ export function registerCommands(context: IContext) {
1818
);
1919

2020
context.subscriptions.push(
21-
registerCommand('codingPlugin.mrTreeItemClick', function ([team, mrItem]: [string, IMRItem]) {
22-
webviewProvider.update({
23-
team,
24-
...mrItem,
25-
});
21+
registerCommand('codingPlugin.mrTreeItemClick', async function ([team, mrItem]: [string, IMRItem]) {
22+
const matchRes = mrItem.path.match(/\/p\/([^/]+)\/d\/([^/]+)\/git\/merge\/([0-9]+)/);
23+
if (matchRes) {
24+
const [, project, repo, mergeRequestIId] = matchRes;
25+
const result = await codingServer.getMrDetail({ team, project, repo, mergeRequestIId });
26+
webviewProvider.update({
27+
session: codingServer.session,
28+
mrItem: result,
29+
repoInfo: {
30+
team,
31+
project,
32+
repo,
33+
},
34+
});
35+
}
2636
}),
2737
);
2838

src/services/codingServer.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ export default class CodingServer {
7979
}
8080
}
8181

82+
async getMrDetail({ team, project, repo, mergeRequestIId }: IRepoInfo & { mergeRequestIId: number }) {
83+
try {
84+
const result = await axios({
85+
method: 'get',
86+
url: `https://${team}.coding.net/api/user/${team}/project/${project}/depot/${repo}/git/merge/${mergeRequestIId}/detail`,
87+
headers: this.getHeaders(),
88+
});
89+
90+
return result.data;
91+
} catch (err) {
92+
console.error(err);
93+
}
94+
}
95+
8296
async getDepotList(team: string = this._repo?.team, project: string = this._repo?.project) {
8397
// TODO: 使用新接口
8498
try {

src/webview.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import hx from 'hbuilderx';
22
import path from 'path';
3+
import toast from './utils/toast';
34

45
interface IMessage {
56
command: string;
6-
text: string;
7+
data: any;
78
}
89

910
export default class WebviewProvider {
@@ -17,11 +18,18 @@ export default class WebviewProvider {
1718
listen() {
1819
this.panel.webView.onDidReceiveMessage((message: IMessage) => {
1920
console.log('webview receive message => ', message);
20-
const { command, text } = message;
21-
if (command === 'webview.mrDetail') {
22-
hx.env.openExternal(text);
23-
} else {
24-
hx.commands.executeCommand(command);
21+
const { command, data } = message;
22+
23+
switch (command) {
24+
case 'webview.mrDetail':
25+
hx.env.openExternal(data);
26+
break;
27+
case 'webview.toast':
28+
toast.error(data);
29+
break;
30+
default:
31+
hx.commands.executeCommand(command);
32+
return;
2533
}
2634
});
2735
}

webviews/App.tsx

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,153 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import cn from 'classnames';
3+
4+
import { closeMergeRequest, mergeMergeRequest, allowMerge } from './services';
5+
import { MERGE_STATUS_TEXT, MERGE_STATUS } from './constants';
36
import style from './style.css';
47

8+
const toast = (msg: string) => {
9+
window.hbuilderx.postMessage({
10+
command: 'webview.toast',
11+
data: msg
12+
});
13+
};
14+
515
const App = () => {
616
const data = JSON.parse(window.__CODING__);
7-
const { team, title, srcBranch, desBranch, author, path } = data;
8-
const url = `https://${team}.coding.net${path}`;
17+
const { session, mrItem, repoInfo } = data;
18+
const { accessToken } = session;
19+
const user = session?.user;
20+
const team = user?.team;
21+
22+
const { can_merge: canMerge, merge_request: mergeRequest } = mrItem;
23+
const { title, srcBranch, desBranch, author, merge_status: mergeStatus } = mergeRequest;
24+
const url = `https://${team}.coding.net${mergeRequest.path}`;
25+
26+
const [isClosing, setIsClosing] = useState(false);
27+
const [isMerging, setIsMerging] = useState(false);
28+
const [isAllowing, setIsAllowing] = useState(false);
29+
const [mrStatus, setMrStatus] = useState(mergeStatus);
30+
const [allowText, setAllowText] = useState('允许合并');
931

1032
const viewOnWeb = () => {
1133
window.hbuilderx.postMessage({
1234
command: 'webview.mrDetail',
13-
text: url
35+
data: url
36+
});
37+
};
38+
39+
const handleClose = async () => {
40+
if (isClosing) return;
41+
42+
setIsClosing(true);
43+
const result = await closeMergeRequest(accessToken, {
44+
...repoInfo,
45+
mergeRequestIId: mergeRequest.iid
46+
});
47+
48+
if (!result.code) {
49+
setMrStatus(MERGE_STATUS.REFUSED);
50+
} else {
51+
toast('关闭合并请求失败');
52+
}
53+
54+
setIsClosing(false);
55+
};
56+
57+
const handleMerge = async () => {
58+
if (isMerging) return;
59+
60+
setIsMerging(true);
61+
const result = await mergeMergeRequest(accessToken, {
62+
...repoInfo,
63+
mergeRequestIId: mergeRequest.iid,
64+
message: `
65+
Accept Merge Request #${mergeRequest.iid}: (${srcBranch} -> ${desBranch})
66+
Merge Request: ${title}
67+
Created By: @${author.name}
68+
Accepted By: @${user.name}
69+
URL: https://${repoInfo.team}.coding.net/p/${repoInfo.project}/d/${repoInfo.repo}/git/merge/${mergeRequest.iid}
70+
`
71+
});
72+
73+
if (!result.code) {
74+
setMrStatus(MERGE_STATUS.ACCEPTED);
75+
} else {
76+
toast('合并请求失败');
77+
}
78+
79+
setIsMerging(false);
80+
};
81+
82+
const handleAllowMerge = async () => {
83+
if (isAllowing) return;
84+
85+
setIsAllowing(true);
86+
const result = await allowMerge(accessToken, {
87+
...repoInfo,
88+
mergeRequestIId: mergeRequest.iid
1489
});
90+
91+
if (!result.code) {
92+
setAllowText('已允许');
93+
} else {
94+
toast('操作失败');
95+
}
96+
97+
setIsAllowing(false);
98+
};
99+
100+
const renderStatus = () => {
101+
const CNS = {
102+
[MERGE_STATUS.CANMERGE]: style.success,
103+
[MERGE_STATUS.CANNOTMERGE]: style.error,
104+
[MERGE_STATUS.ACCEPTED]: style.merged
105+
};
106+
return <span className={cn(style.status, CNS[mrStatus])}>{MERGE_STATUS_TEXT[mrStatus]}</span>;
15107
};
16108

109+
const showCloseBtn = (canMerge || user.id === mergeRequest.author.id) && mrStatus !== MERGE_STATUS.REFUSED && mrStatus !== MERGE_STATUS.ACCEPTED;
110+
const mrStatusOk = mrStatus === MERGE_STATUS.CANMERGE || mrStatus === MERGE_STATUS.CANNOTMERGE;
111+
const showMergeBtn = mrStatus === MERGE_STATUS.CANMERGE;
112+
const showAllowMergeBtn = mrStatusOk && author.id !== user.id;
113+
17114
return (
18115
<div className={style.root}>
19116
<a onClick={viewOnWeb}>前往 web 端查看</a>
20117

21118
<div className={style.title}>
22-
{title}
119+
{title} {renderStatus()}
23120
</div>
24121
<div>{`将分支 ${srcBranch} 合并到分支 ${desBranch}`}</div>
25122
<div>创建人:{author.name}</div>
26123

27124
<div className={style.btnGroup}>
28-
<div className={cn(style.btn, style.btnPrimary)}>合并</div>
29-
<div className={cn(style.btn, style.btnPrimary)}>允许合并</div>
30-
<div className={style.btn}>关闭</div>
125+
{showMergeBtn && (
126+
<div
127+
className={cn(style.btn, style.btnPrimary, isMerging && style.disabled)}
128+
onClick={handleMerge}
129+
>
130+
{isMerging ? '合并中...' : '合并'}
131+
</div>
132+
)}
133+
134+
{showAllowMergeBtn && (
135+
<div
136+
className={cn(style.btn, style.btnPrimary, isAllowing && style.disabled)}
137+
onClick={handleAllowMerge}
138+
>
139+
{allowText}
140+
</div>
141+
)}
142+
143+
{showCloseBtn && (
144+
<div
145+
className={cn(style.btn, isClosing && style.disabled)}
146+
onClick={handleClose}
147+
>
148+
{isClosing ? '关闭中...' : '关闭'}
149+
</div>
150+
)}
31151
</div>
32152
</div>
33153
);

webviews/constants/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export const enum MERGE_STATUS {
2+
ACCEPTED = 'ACCEPTED',
3+
REFUSED = 'REFUSED',
4+
CANMERGE = 'CANMERGE',
5+
CANNOTMERGE = 'CANNOTMERGE'
6+
}
7+
8+
export const MERGE_STATUS_TEXT = {
9+
[MERGE_STATUS.ACCEPTED]: '已合并',
10+
[MERGE_STATUS.CANMERGE]: '可合并',
11+
[MERGE_STATUS.REFUSED]: '已关闭',
12+
[MERGE_STATUS.CANNOTMERGE]: '不可自动合并'
13+
};

webviews/services/index.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import axios from 'axios';
2+
import qs from 'querystring';
3+
4+
interface IMergeRequestParams {
5+
team: string;
6+
project: string;
7+
repo: string;
8+
mergeRequestIId: number;
9+
}
10+
11+
interface IMergeMergeRequestParams extends IMergeRequestParams {
12+
message: string;
13+
del_source_branch: boolean;
14+
fastforward: boolean;
15+
squash: boolean;
16+
}
17+
18+
const getHeaders = (token: string) => ({
19+
Authorization: `token ${token}`,
20+
});
21+
22+
export const getMergeRequestDetail = async (token: string, { team, project, repo, mergeRequestIId }: IMergeRequestParams) => {
23+
const result = await axios({
24+
method: 'get',
25+
url: `https://${team}.coding.net/api/user/${team}/project/${project}/depot/${repo}/git/merge/${mergeRequestIId}/detail`,
26+
headers: getHeaders(token)
27+
});
28+
29+
return result.data;
30+
};
31+
32+
export const closeMergeRequest = async (token: string, { team, project, repo, mergeRequestIId }: IMergeRequestParams) => {
33+
const result = await axios({
34+
method: 'post',
35+
url: `https://${team}.coding.net/api/user/${team}/project/${project}/depot/${repo}/git/merge/${mergeRequestIId}/refuse`,
36+
headers: getHeaders(token)
37+
});
38+
39+
return result.data;
40+
};
41+
42+
export const mergeMergeRequest = async (token: string, { team, project, repo, mergeRequestIId, ...others }: IMergeMergeRequestParams) => {
43+
const result = await axios({
44+
method: 'post',
45+
url: `https://${team}.coding.net/api/user/${team}/project/${project}/depot/${repo}/git/merge/${mergeRequestIId}/merge`,
46+
headers: {
47+
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
48+
...getHeaders(token),
49+
},
50+
data: qs.stringify({
51+
...others,
52+
del_source_branch: false,
53+
fastforward: false,
54+
squash: false
55+
})
56+
});
57+
58+
return result.data;
59+
};
60+
61+
export const allowMerge = async (token: string, { team, project, repo, mergeRequestIId }: IMergeRequestParams) => {
62+
const result = await axios({
63+
method: 'post',
64+
url: `https://${team}.coding.net/api/user/${team}/project/${project}/depot/${repo}/git/merge/${mergeRequestIId}/good`,
65+
headers: getHeaders(token)
66+
});
67+
return result.data;
68+
};

webviews/style.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,35 @@ a:hover {
4242
opacity: 0.8;
4343
}
4444

45+
.disabled {
46+
cursor: not-allowed;
47+
opacity: 0.5;
48+
}
49+
4550
.btnPrimary {
4651
border: none;
4752
background-color: #06f;
4853
color: #fff;
4954
}
55+
56+
.status {
57+
display: inline-flex;
58+
align-items: center;
59+
justify-content: center;
60+
margin-left: 10px;
61+
padding: 3px 5px;
62+
border-radius: 2px;
63+
font-size: 13px;
64+
color: black;
65+
background-color: #e6e9ed;
66+
}
67+
68+
.status.success {
69+
background-color: #bbfada;
70+
}
71+
.status.error {
72+
background-color: red;
73+
}
74+
.status.merged {
75+
background-color: #cceaff;
76+
}

0 commit comments

Comments
 (0)