Skip to content

Commit 11f6716

Browse files
committed
feat(mr): status check.
1 parent c6483d2 commit 11f6716

19 files changed

+307
-60
lines changed

src/codingServer.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
IMemberListResp,
1818
IMRContentResp,
1919
ICreateCommentResp,
20+
IMRStatusResp,
2021
} from 'src/typings/respResult';
2122
import { PromiseAdapter, promiseFromEvent, parseQuery, parseCloneUrl } from 'src/common/utils';
2223
import { GitService } from 'src/common/gitService';
@@ -96,6 +97,13 @@ export class CodingServer {
9697
await vscode.commands.executeCommand('setContext', 'hasRepo', !!repoInfo?.repo);
9798
const result = await this.getUserInfo(repoInfo?.team || ``, accessToken);
9899
const { data: userInfo } = result;
100+
101+
if (userInfo.team !== repoInfo?.team) {
102+
this._loggedIn = false;
103+
await vscode.commands.executeCommand('setContext', 'loggedIn', this._loggedIn);
104+
throw new Error(`team not match`);
105+
}
106+
99107
const ret: ISessionData = {
100108
id: nanoid(),
101109
user: userInfo,
@@ -686,6 +694,27 @@ export class CodingServer {
686694
}
687695
}
688696

697+
public async fetchMRStatus(iid: string) {
698+
try {
699+
const { repoApiPrefix } = await this.getApiPrefix();
700+
const resp: IMRStatusResp = await got
701+
.get(`${repoApiPrefix}/merge/${iid}/commit-statuses`, {
702+
searchParams: {
703+
access_token: this._session?.accessToken,
704+
},
705+
})
706+
.json();
707+
708+
if (resp.code) {
709+
return Promise.reject(resp);
710+
}
711+
712+
return resp;
713+
} catch (e) {
714+
return Promise.reject(e);
715+
}
716+
}
717+
689718
get loggedIn() {
690719
return this._loggedIn;
691720
}

src/panel.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ export class Panel {
152152
} catch (e) {}
153153
break;
154154
}
155+
case `mr.fetch.status`: {
156+
try {
157+
const { iid } = args;
158+
const resp = await this._codingSrv.fetchMRStatus(iid);
159+
this._replyMessage(message, resp.data);
160+
} catch (e) {}
161+
break;
162+
}
155163
default:
156164
return this.MESSAGE_UNHANDLED;
157165
}

src/tree/mrTree.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ enum ItemType {
2020
Node = `node`,
2121
}
2222

23+
const capitalized = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
24+
2325
const getIcon = (name: string, theme: string) =>
2426
path.join(__filename, `../../../assets/${theme}/${name}.png`);
2527

@@ -58,7 +60,7 @@ export class MRTreeDataProvider implements vscode.TreeDataProvider<ListItem<ITre
5860
private _disposables: vscode.Disposable[];
5961

6062
private _context: vscode.ExtensionContext;
61-
private _service: CodingServer;
63+
private readonly _service: CodingServer;
6264

6365
constructor(context: vscode.ExtensionContext, service: CodingServer) {
6466
this._context = context;
@@ -83,7 +85,7 @@ export class MRTreeDataProvider implements vscode.TreeDataProvider<ListItem<ITre
8385

8486
getChildren(element?: ListItem<ITreeNode>): Thenable<ListItem<ITreeNode>[]> {
8587
if (!this._service.loggedIn) {
86-
vscode.window.showErrorMessage(`[MR Tree] Invalid credentials.`);
88+
console.error(`[MR Tree] Invalid credentials.`);
8789
return Promise.resolve([]);
8890
}
8991

@@ -94,17 +96,13 @@ export class MRTreeDataProvider implements vscode.TreeDataProvider<ListItem<ITre
9496

9597
if (!element) {
9698
return Promise.resolve([
99+
new CategoryItem(capitalized(MRType.Open), MRType.Open, TreeItemCollapsibleState.Collapsed),
97100
new CategoryItem(
98-
MRType.Open.toUpperCase(),
99-
MRType.Open,
100-
TreeItemCollapsibleState.Collapsed,
101-
),
102-
new CategoryItem(
103-
MRType.Closed.toUpperCase(),
101+
capitalized(MRType.Closed),
104102
MRType.Closed,
105103
TreeItemCollapsibleState.Collapsed,
106104
),
107-
new CategoryItem(MRType.All.toUpperCase(), MRType.All, TreeItemCollapsibleState.Collapsed),
105+
new CategoryItem(capitalized(MRType.All), MRType.All, TreeItemCollapsibleState.Collapsed),
108106
]);
109107
}
110108

@@ -115,7 +113,7 @@ export class MRTreeDataProvider implements vscode.TreeDataProvider<ListItem<ITre
115113
.then((resp) => {
116114
if (resp.code) {
117115
const msg = Object.values(resp.msg || {})[0];
118-
vscode.window.showErrorMessage(`[MR] list: ${msg}`);
116+
console.error(`[MR] list: ${msg}`);
119117
return [];
120118
}
121119

src/typings/respResult.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,3 +244,25 @@ export interface IMRContentResp extends CodingResponse {
244244
export interface ICreateCommentResp extends CodingResponse {
245245
data: IComment;
246246
}
247+
248+
export interface IMRStatusItem {
249+
state: string;
250+
sha: string;
251+
context: string;
252+
target_url: string;
253+
description: string;
254+
ignore: boolean;
255+
}
256+
257+
export interface IMRStatus {
258+
state: string;
259+
pendingStateCount: number;
260+
successStateCount: number;
261+
failureStateCount: number;
262+
errorStateCount: number;
263+
statuses: IMRStatusItem[];
264+
}
265+
266+
export interface IMRStatusResp extends CodingResponse {
267+
data: IMRStatus;
268+
}

webviews/App.tsx

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
import React, { FormEvent, useRef, useState } from 'react';
1+
import React, { FormEvent, useEffect, useRef, useState, useCallback } from 'react';
22
import { view } from '@risingstack/react-easy-state';
33

4+
import { IMRStatus } from 'src/typings/respResult';
5+
46
import appStore from 'webviews/store/appStore';
57
import persistDataHook from 'webviews/hooks/persistDataHook';
6-
import Activities from 'webviews/components/Activities';
7-
import Reviewers from 'webviews/components/Reviewers';
88
import initDataHook from 'webviews/hooks/initDataHook';
9-
import EditButton from 'webviews/components/EditButton';
10-
// import { requestUpdateMRContent } from 'webviews/service/mrService';
9+
10+
import Activities from 'webviews/components/mr/Activities';
11+
import Reviewers from 'webviews/components/mr/Reviewers';
12+
import EditButton from 'webviews/components/mr/EditButton';
13+
import StatusCheck from 'webviews/components/mr/StatusCheck';
1114

1215
import {
1316
EmptyWrapper,
@@ -25,18 +28,46 @@ import {
2528
} from 'webviews/app.styles';
2629

2730
function App() {
28-
const { currentMR, updateMRTitle, toggleUpdatingDesc, updateMRDesc } = appStore;
31+
const { currentMR, updateMRTitle, toggleUpdatingDesc, updateMRDesc, fetchMRStatus } = appStore;
2932
const [isEditingTitle, setEditingTitle] = useState(false);
3033
const [title, setTitle] = useState(currentMR?.data?.merge_request?.title);
3134
const inputRef = useRef<HTMLInputElement | null>(null);
3235
const [desc, setDesc] = useState(``);
36+
const statusChecker = useRef<undefined | number>();
37+
const [statusData, setStatusData] = useState<IMRStatus | null>(null);
3338

3439
const { repoInfo, data } = currentMR;
3540
const { merge_request: mergeRequest } = data || {};
3641

3742
persistDataHook();
3843
initDataHook();
3944

45+
useEffect(() => {
46+
statusChecker.current = window.setTimeout(async () => {
47+
try {
48+
await onRefreshStatus();
49+
} finally {
50+
window.clearTimeout(statusChecker.current);
51+
52+
statusChecker.current = window.setInterval(async () => {
53+
await onRefreshStatus();
54+
}, 30 * 1000);
55+
}
56+
}, 5 * 1000);
57+
58+
return () => {
59+
window.clearTimeout(statusChecker.current);
60+
window.clearInterval(statusChecker.current);
61+
setStatusData(null);
62+
};
63+
}, [currentMR.iid]);
64+
65+
const onRefreshStatus = useCallback(async () => {
66+
const resp = await fetchMRStatus(currentMR.iid);
67+
setStatusData(resp);
68+
return null;
69+
}, [currentMR.iid]);
70+
4071
const handleKeyDown = async (event: any) => {
4172
if (event.key === 'Enter') {
4273
await updateMRTitle(title);
@@ -146,6 +177,9 @@ function App() {
146177
onChange={onChangeDesc}
147178
/>
148179
)}
180+
181+
<StatusCheck data={statusData} onRefresh={onRefreshStatus} />
182+
149183
<SectionTitle>Activities</SectionTitle>
150184
<Activities />
151185
</Body>

webviews/assets/edit.svg

Lines changed: 1 addition & 1 deletion
Loading

webviews/assets/refresh.svg

Lines changed: 54 additions & 0 deletions
Loading

webviews/components/EditButton.tsx

Lines changed: 0 additions & 41 deletions
This file was deleted.

webviews/components/IconButton.tsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
import styled, { css, keyframes } from 'styled-components';
3+
4+
interface Props {
5+
onClick?: () => void;
6+
children: React.ReactElement;
7+
title?: string;
8+
width?: number;
9+
height?: number;
10+
rotate?: boolean;
11+
}
12+
13+
const rotate = keyframes`
14+
0% {
15+
transform: rotate(0deg);
16+
}
17+
18+
100% {
19+
transform: rotate(360deg);
20+
}
21+
`;
22+
23+
const Button = styled.button`
24+
border: unset;
25+
background: unset;
26+
width: 20px;
27+
height: 20px;
28+
margin-left: 1ex;
29+
padding: 2px 0;
30+
vertical-align: middle;
31+
32+
:hover {
33+
cursor: pointer;
34+
}
35+
36+
:focus {
37+
outline: 1px solid var(--vscode-focusBorder);
38+
outline-offset: 2px;
39+
}
40+
41+
svg {
42+
width: ${(props: Props) => props.height || 16}px;
43+
height: ${(props: Props) => props.width || 16}px;
44+
overflow: hidden;
45+
}
46+
47+
svg path {
48+
fill: var(--vscode-foreground);
49+
}
50+
`;
51+
52+
const IconButton = ({
53+
onClick = () => null,
54+
children,
55+
title = ``,
56+
width,
57+
height,
58+
rotate = false,
59+
}: Props) => {
60+
return (
61+
<Button title={title} width={width} height={height} rotate={rotate} onClick={onClick}>
62+
{children}
63+
</Button>
64+
);
65+
};
66+
67+
export default IconButton;
File renamed without changes.

0 commit comments

Comments
 (0)