github-issue-tower-defence-management 1.102.0 → 1.103.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +1 -0
- package/bin/adapter/entry-points/console/consoleOperationApi.js +37 -1
- package/bin/adapter/entry-points/console/consoleOperationApi.js.map +1 -1
- package/bin/adapter/entry-points/console/consoleServer.js +2 -0
- package/bin/adapter/entry-points/console/consoleServer.js.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-CO-f_j4f.css +1 -0
- package/bin/adapter/entry-points/console/ui-dist/assets/index-CP3-MdUU.js +101 -0
- package/bin/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +71 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/console/consoleOperationApi.test.ts +96 -0
- package/src/adapter/entry-points/console/consoleOperationApi.ts +55 -1
- package/src/adapter/entry-points/console/consoleServer.ts +3 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleChangedFileList.tsx +25 -4
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.stories.tsx +9 -0
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.test.tsx +88 -1
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleFileDiff.tsx +197 -12
- package/src/adapter/entry-points/console/ui/src/features/console/components/detail/ConsoleItemDetail.tsx +4 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts +23 -0
- package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts +33 -0
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.test.ts +50 -1
- package/src/adapter/entry-points/console/ui/src/features/console/lib/consoleApi.ts +27 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.test.tsx +1 -0
- package/src/adapter/entry-points/console/ui/src/features/console/pages/ConsoleItemDetailContainer.tsx +11 -0
- package/src/adapter/entry-points/console/ui/src/index.css +104 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-CO-f_j4f.css +1 -0
- package/src/adapter/entry-points/console/ui-dist/assets/index-CP3-MdUU.js +101 -0
- package/src/adapter/entry-points/console/ui-dist/index.html +2 -2
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +106 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +108 -0
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +9 -0
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts +1 -0
- package/types/adapter/entry-points/console/consoleOperationApi.d.ts.map +1 -1
- package/types/adapter/entry-points/console/consoleServer.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +4 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +2 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/bin/adapter/entry-points/console/ui-dist/assets/index-CFGeqgfE.js +0 -101
- package/bin/adapter/entry-points/console/ui-dist/assets/index-ES6SLB1Y.css +0 -1
- package/src/adapter/entry-points/console/ui-dist/assets/index-CFGeqgfE.js +0 -101
- package/src/adapter/entry-points/console/ui-dist/assets/index-ES6SLB1Y.css +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
1
|
+
import { fireEvent, render, waitFor } from '@testing-library/react';
|
|
2
2
|
import { consoleChangedFilesFixture } from '../../testing/fixtures';
|
|
3
3
|
import { ConsoleFileDiff } from './ConsoleFileDiff';
|
|
4
4
|
|
|
@@ -21,4 +21,91 @@ describe('ConsoleFileDiff', () => {
|
|
|
21
21
|
const { getByText } = render(<ConsoleFileDiff patch={null} />);
|
|
22
22
|
expect(getByText('(no diff / binary or too large)')).toBeInTheDocument();
|
|
23
23
|
});
|
|
24
|
+
|
|
25
|
+
it('does not render any comment affordance without an onAddInlineComment handler', () => {
|
|
26
|
+
const { container } = render(
|
|
27
|
+
<ConsoleFileDiff
|
|
28
|
+
patch={consoleChangedFilesFixture[0].patch}
|
|
29
|
+
path="src/adapter/entry-points/console/consoleServer.ts"
|
|
30
|
+
/>,
|
|
31
|
+
);
|
|
32
|
+
expect(
|
|
33
|
+
container.querySelectorAll('.console-diff-comment-button').length,
|
|
34
|
+
).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('anchors an added line comment to its new line on the RIGHT side', async () => {
|
|
38
|
+
const onAddInlineComment = jest.fn().mockResolvedValue(undefined);
|
|
39
|
+
const { getByLabelText, getByPlaceholderText, getByText } = render(
|
|
40
|
+
<ConsoleFileDiff
|
|
41
|
+
patch={consoleChangedFilesFixture[0].patch}
|
|
42
|
+
path="src/adapter/entry-points/console/consoleServer.ts"
|
|
43
|
+
onAddInlineComment={onAddInlineComment}
|
|
44
|
+
/>,
|
|
45
|
+
);
|
|
46
|
+
fireEvent.click(getByLabelText('Comment on line 57 (RIGHT)'));
|
|
47
|
+
fireEvent.change(
|
|
48
|
+
getByPlaceholderText('Leave a review comment on this line…'),
|
|
49
|
+
{ target: { value: 'Why npm ci here?' } },
|
|
50
|
+
);
|
|
51
|
+
fireEvent.click(getByText('Comment'));
|
|
52
|
+
await waitFor(() =>
|
|
53
|
+
expect(onAddInlineComment).toHaveBeenCalledWith(
|
|
54
|
+
'src/adapter/entry-points/console/consoleServer.ts',
|
|
55
|
+
57,
|
|
56
|
+
'RIGHT',
|
|
57
|
+
'Why npm ci here?',
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('anchors a removed line comment to its old line on the LEFT side', async () => {
|
|
63
|
+
const onAddInlineComment = jest.fn().mockResolvedValue(undefined);
|
|
64
|
+
const { getByLabelText, getByPlaceholderText, getByText } = render(
|
|
65
|
+
<ConsoleFileDiff
|
|
66
|
+
patch={consoleChangedFilesFixture[0].patch}
|
|
67
|
+
path="src/adapter/entry-points/console/consoleServer.ts"
|
|
68
|
+
onAddInlineComment={onAddInlineComment}
|
|
69
|
+
/>,
|
|
70
|
+
);
|
|
71
|
+
fireEvent.click(getByLabelText('Comment on line 57 (LEFT)'));
|
|
72
|
+
fireEvent.change(
|
|
73
|
+
getByPlaceholderText('Leave a review comment on this line…'),
|
|
74
|
+
{ target: { value: 'Keep npm install?' } },
|
|
75
|
+
);
|
|
76
|
+
fireEvent.click(getByText('Comment'));
|
|
77
|
+
await waitFor(() =>
|
|
78
|
+
expect(onAddInlineComment).toHaveBeenCalledWith(
|
|
79
|
+
'src/adapter/entry-points/console/consoleServer.ts',
|
|
80
|
+
57,
|
|
81
|
+
'LEFT',
|
|
82
|
+
'Keep npm install?',
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('shows the surfaced error when posting an inline comment fails', async () => {
|
|
88
|
+
const onAddInlineComment = jest
|
|
89
|
+
.fn()
|
|
90
|
+
.mockRejectedValue(new Error('line must be part of the diff'));
|
|
91
|
+
const { getByLabelText, getByPlaceholderText, getByText, getByRole } =
|
|
92
|
+
render(
|
|
93
|
+
<ConsoleFileDiff
|
|
94
|
+
patch={consoleChangedFilesFixture[0].patch}
|
|
95
|
+
path="src/adapter/entry-points/console/consoleServer.ts"
|
|
96
|
+
onAddInlineComment={onAddInlineComment}
|
|
97
|
+
/>,
|
|
98
|
+
);
|
|
99
|
+
fireEvent.click(getByLabelText('Comment on line 58 (RIGHT)'));
|
|
100
|
+
fireEvent.change(
|
|
101
|
+
getByPlaceholderText('Leave a review comment on this line…'),
|
|
102
|
+
{ target: { value: 'A comment that will fail.' } },
|
|
103
|
+
);
|
|
104
|
+
fireEvent.click(getByText('Comment'));
|
|
105
|
+
await waitFor(() =>
|
|
106
|
+
expect(getByRole('alert')).toHaveTextContent(
|
|
107
|
+
'line must be part of the diff',
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
});
|
|
24
111
|
});
|
|
@@ -1,29 +1,214 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Fragment, useState } from 'react';
|
|
2
|
+
import type { ConsoleReviewCommentSide } from '../../lib/consoleApi';
|
|
3
|
+
import { type ConsoleDiffLine, parseUnifiedDiff } from '../../logic/diff';
|
|
4
|
+
|
|
5
|
+
export type ConsoleInlineCommentTarget = {
|
|
6
|
+
path: string;
|
|
7
|
+
line: number;
|
|
8
|
+
side: ConsoleReviewCommentSide;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type ConsoleAddInlineComment = (
|
|
12
|
+
path: string,
|
|
13
|
+
line: number,
|
|
14
|
+
side: ConsoleReviewCommentSide,
|
|
15
|
+
body: string,
|
|
16
|
+
) => Promise<void>;
|
|
2
17
|
|
|
3
18
|
export type ConsoleFileDiffProps = {
|
|
4
19
|
patch: string | null;
|
|
20
|
+
path?: string;
|
|
21
|
+
onAddInlineComment?: ConsoleAddInlineComment;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type ComposerStatus =
|
|
25
|
+
| { kind: 'idle' }
|
|
26
|
+
| { kind: 'posting' }
|
|
27
|
+
| { kind: 'error'; message: string }
|
|
28
|
+
| { kind: 'posted' };
|
|
29
|
+
|
|
30
|
+
const commentTargetForRow = (
|
|
31
|
+
row: ConsoleDiffLine,
|
|
32
|
+
): { line: number; side: ConsoleReviewCommentSide } | null => {
|
|
33
|
+
if (row.kind === 'add' || row.kind === 'ctx') {
|
|
34
|
+
if (row.newLineNumber === null) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return { line: row.newLineNumber, side: 'RIGHT' };
|
|
38
|
+
}
|
|
39
|
+
if (row.kind === 'del') {
|
|
40
|
+
if (row.oldLineNumber === null) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return { line: row.oldLineNumber, side: 'LEFT' };
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
5
46
|
};
|
|
6
47
|
|
|
7
|
-
|
|
48
|
+
const rowKey = (row: ConsoleDiffLine): string =>
|
|
49
|
+
`${row.kind}:${row.oldLineNumber ?? 'x'}:${row.newLineNumber ?? 'x'}:${row.content}`;
|
|
50
|
+
|
|
51
|
+
type ConsoleInlineCommentComposerProps = {
|
|
52
|
+
target: ConsoleInlineCommentTarget;
|
|
53
|
+
onSubmit: ConsoleAddInlineComment;
|
|
54
|
+
onClose: () => void;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const ConsoleInlineCommentComposer = ({
|
|
58
|
+
target,
|
|
59
|
+
onSubmit,
|
|
60
|
+
onClose,
|
|
61
|
+
}: ConsoleInlineCommentComposerProps) => {
|
|
62
|
+
const [draft, setDraft] = useState<string>('');
|
|
63
|
+
const [status, setStatus] = useState<ComposerStatus>({ kind: 'idle' });
|
|
64
|
+
|
|
65
|
+
const submit = async (): Promise<void> => {
|
|
66
|
+
const body = draft.trim();
|
|
67
|
+
if (body.length === 0 || status.kind === 'posting') {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
setStatus({ kind: 'posting' });
|
|
71
|
+
try {
|
|
72
|
+
await onSubmit(target.path, target.line, target.side, body);
|
|
73
|
+
setDraft('');
|
|
74
|
+
setStatus({ kind: 'posted' });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
setStatus({
|
|
77
|
+
kind: 'error',
|
|
78
|
+
message: error instanceof Error ? error.message : 'failed to post',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className="console-diff-composer">
|
|
85
|
+
<div className="console-diff-composer-anchor">
|
|
86
|
+
commenting on line {target.line} ({target.side})
|
|
87
|
+
</div>
|
|
88
|
+
{status.kind === 'posted' ? (
|
|
89
|
+
<p className="console-diff-composer-posted">Comment posted.</p>
|
|
90
|
+
) : (
|
|
91
|
+
<>
|
|
92
|
+
<textarea
|
|
93
|
+
className="console-diff-composer-input"
|
|
94
|
+
rows={3}
|
|
95
|
+
placeholder="Leave a review comment on this line…"
|
|
96
|
+
value={draft}
|
|
97
|
+
onChange={(event) => setDraft(event.target.value)}
|
|
98
|
+
/>
|
|
99
|
+
<div className="console-diff-composer-controls">
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
className="console-diff-composer-submit"
|
|
103
|
+
disabled={status.kind === 'posting'}
|
|
104
|
+
onClick={() => {
|
|
105
|
+
void submit();
|
|
106
|
+
}}
|
|
107
|
+
>
|
|
108
|
+
Comment
|
|
109
|
+
</button>
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
className="console-diff-composer-cancel"
|
|
113
|
+
onClick={onClose}
|
|
114
|
+
>
|
|
115
|
+
Cancel
|
|
116
|
+
</button>
|
|
117
|
+
{status.kind === 'posting' && (
|
|
118
|
+
<span className="console-diff-composer-status">Posting…</span>
|
|
119
|
+
)}
|
|
120
|
+
{status.kind === 'error' && (
|
|
121
|
+
<span
|
|
122
|
+
role="alert"
|
|
123
|
+
className="console-diff-composer-status console-diff-composer-error"
|
|
124
|
+
>
|
|
125
|
+
Failed: {status.message}
|
|
126
|
+
</span>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
</>
|
|
130
|
+
)}
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const ConsoleFileDiff = ({
|
|
136
|
+
patch,
|
|
137
|
+
path,
|
|
138
|
+
onAddInlineComment,
|
|
139
|
+
}: ConsoleFileDiffProps) => {
|
|
140
|
+
const [activeTarget, setActiveTarget] =
|
|
141
|
+
useState<ConsoleInlineCommentTarget | null>(null);
|
|
142
|
+
|
|
8
143
|
if (patch === null || patch === '') {
|
|
9
144
|
return (
|
|
10
145
|
<p className="console-file-diff-empty">(no diff / binary or too large)</p>
|
|
11
146
|
);
|
|
12
147
|
}
|
|
13
148
|
const rows = parseUnifiedDiff(patch);
|
|
149
|
+
const canComment =
|
|
150
|
+
onAddInlineComment !== undefined && path !== undefined && path.length > 0;
|
|
151
|
+
const columnCount = canComment ? 4 : 3;
|
|
152
|
+
|
|
14
153
|
return (
|
|
15
154
|
<table className="console-file-diff">
|
|
16
155
|
<tbody>
|
|
17
|
-
{rows.map((row) =>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
156
|
+
{rows.map((row) => {
|
|
157
|
+
const key = rowKey(row);
|
|
158
|
+
const target = canComment ? commentTargetForRow(row) : null;
|
|
159
|
+
const isActive =
|
|
160
|
+
activeTarget !== null &&
|
|
161
|
+
target !== null &&
|
|
162
|
+
activeTarget.line === target.line &&
|
|
163
|
+
activeTarget.side === target.side;
|
|
164
|
+
return (
|
|
165
|
+
<Fragment key={key}>
|
|
166
|
+
<tr className={`console-diff-row console-diff-${row.kind}`}>
|
|
167
|
+
{canComment && (
|
|
168
|
+
<td className="console-diff-comment-cell">
|
|
169
|
+
{target !== null && path !== undefined && (
|
|
170
|
+
<button
|
|
171
|
+
type="button"
|
|
172
|
+
className="console-diff-comment-button"
|
|
173
|
+
aria-label={`Comment on line ${target.line} (${target.side})`}
|
|
174
|
+
aria-expanded={isActive}
|
|
175
|
+
onClick={() =>
|
|
176
|
+
setActiveTarget(
|
|
177
|
+
isActive
|
|
178
|
+
? null
|
|
179
|
+
: {
|
|
180
|
+
path,
|
|
181
|
+
line: target.line,
|
|
182
|
+
side: target.side,
|
|
183
|
+
},
|
|
184
|
+
)
|
|
185
|
+
}
|
|
186
|
+
>
|
|
187
|
+
+
|
|
188
|
+
</button>
|
|
189
|
+
)}
|
|
190
|
+
</td>
|
|
191
|
+
)}
|
|
192
|
+
<td className="console-diff-ln">{row.oldLineNumber ?? ''}</td>
|
|
193
|
+
<td className="console-diff-ln">{row.newLineNumber ?? ''}</td>
|
|
194
|
+
<td className="console-diff-code">{row.content}</td>
|
|
195
|
+
</tr>
|
|
196
|
+
{isActive &&
|
|
197
|
+
onAddInlineComment !== undefined &&
|
|
198
|
+
activeTarget !== null && (
|
|
199
|
+
<tr className="console-diff-composer-row">
|
|
200
|
+
<td colSpan={columnCount}>
|
|
201
|
+
<ConsoleInlineCommentComposer
|
|
202
|
+
target={activeTarget}
|
|
203
|
+
onSubmit={onAddInlineComment}
|
|
204
|
+
onClose={() => setActiveTarget(null)}
|
|
205
|
+
/>
|
|
206
|
+
</td>
|
|
207
|
+
</tr>
|
|
208
|
+
)}
|
|
209
|
+
</Fragment>
|
|
210
|
+
);
|
|
211
|
+
})}
|
|
27
212
|
</tbody>
|
|
28
213
|
</table>
|
|
29
214
|
);
|
|
@@ -20,6 +20,7 @@ import { ConsolePanel } from '../layout/ConsolePanel';
|
|
|
20
20
|
import { ConsoleChangedFileList } from './ConsoleChangedFileList';
|
|
21
21
|
import { ConsoleCommentList } from './ConsoleCommentList';
|
|
22
22
|
import { ConsoleCommitList } from './ConsoleCommitList';
|
|
23
|
+
import type { ConsoleAddInlineComment } from './ConsoleFileDiff';
|
|
23
24
|
import { ConsoleItemIcon } from './ConsoleItemIcon';
|
|
24
25
|
import { ConsolePullRequestDetail } from './ConsolePullRequestDetail';
|
|
25
26
|
|
|
@@ -56,6 +57,7 @@ export type ConsoleItemDetailProps = {
|
|
|
56
57
|
commentComposer: ReactNode;
|
|
57
58
|
operationBar: ReactNode;
|
|
58
59
|
buildImageProxyUrl?: ImageProxyUrlBuilder;
|
|
60
|
+
onAddInlineComment?: ConsoleAddInlineComment;
|
|
59
61
|
};
|
|
60
62
|
|
|
61
63
|
export const ConsoleItemDetail = ({
|
|
@@ -81,6 +83,7 @@ export const ConsoleItemDetail = ({
|
|
|
81
83
|
commentComposer,
|
|
82
84
|
operationBar,
|
|
83
85
|
buildImageProxyUrl,
|
|
86
|
+
onAddInlineComment,
|
|
84
87
|
}: ConsoleItemDetailProps) => {
|
|
85
88
|
const resolvedState = state?.state ?? 'open';
|
|
86
89
|
const merged = state?.merged ?? false;
|
|
@@ -208,6 +211,7 @@ export const ConsoleItemDetail = ({
|
|
|
208
211
|
files={files}
|
|
209
212
|
isLoading={filesAreLoading}
|
|
210
213
|
error={filesError}
|
|
214
|
+
onAddInlineComment={onAddInlineComment}
|
|
211
215
|
/>
|
|
212
216
|
</ConsolePanel>
|
|
213
217
|
)}
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.test.ts
CHANGED
|
@@ -214,6 +214,29 @@ describe('useConsoleOperations', () => {
|
|
|
214
214
|
});
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
+
it('posts a line-anchored inline review comment to the reviewcomment endpoint', async () => {
|
|
218
|
+
const fetchMock = captureFetch();
|
|
219
|
+
const { result } = setup();
|
|
220
|
+
await act(async () => {
|
|
221
|
+
await result.current.operations.addInlineReviewComment(
|
|
222
|
+
prItem.url,
|
|
223
|
+
'src/index.ts',
|
|
224
|
+
42,
|
|
225
|
+
'RIGHT',
|
|
226
|
+
'Consider extracting this into a helper.',
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
expect(fetchMock.mock.calls[0][0]).toBe('/api/reviewcomment?k=token');
|
|
230
|
+
expect(lastBody(fetchMock)).toEqual({
|
|
231
|
+
pjcode: 'umino',
|
|
232
|
+
url: prItem.url,
|
|
233
|
+
path: 'src/index.ts',
|
|
234
|
+
line: 42,
|
|
235
|
+
side: 'RIGHT',
|
|
236
|
+
body: 'Consider extracting this into a helper.',
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
217
240
|
it('rejects an operation and posts nothing when no pjcode is available', async () => {
|
|
218
241
|
const fetchMock = captureFetch();
|
|
219
242
|
localStorage.clear();
|
package/src/adapter/entry-points/console/ui/src/features/console/hooks/useConsoleOperations.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
type ConsoleIntmuxRequest,
|
|
4
|
+
type ConsoleReviewCommentSide,
|
|
4
5
|
type ConsoleReviewRequest,
|
|
5
6
|
type ConsoleTriageRequest,
|
|
6
7
|
postConsoleComment,
|
|
7
8
|
postConsoleOperation,
|
|
9
|
+
postConsoleReviewComment,
|
|
8
10
|
} from '../lib/consoleApi';
|
|
9
11
|
import {
|
|
10
12
|
type ConsoleCloseAction,
|
|
@@ -54,6 +56,13 @@ export type ConsoleOperationsApi = {
|
|
|
54
56
|
action: ConsoleCloseAction,
|
|
55
57
|
) => Promise<void>;
|
|
56
58
|
addComment: (item: ConsoleListItem, body: string) => Promise<ConsoleComment>;
|
|
59
|
+
addInlineReviewComment: (
|
|
60
|
+
prUrl: string,
|
|
61
|
+
path: string,
|
|
62
|
+
line: number,
|
|
63
|
+
side: ConsoleReviewCommentSide,
|
|
64
|
+
body: string,
|
|
65
|
+
) => Promise<void>;
|
|
57
66
|
};
|
|
58
67
|
|
|
59
68
|
const reviewRequest = (
|
|
@@ -247,6 +256,29 @@ export const useConsoleOperations = (
|
|
|
247
256
|
[pjcode, appendToken],
|
|
248
257
|
);
|
|
249
258
|
|
|
259
|
+
const addInlineReviewComment = useCallback(
|
|
260
|
+
async (
|
|
261
|
+
prUrl: string,
|
|
262
|
+
path: string,
|
|
263
|
+
line: number,
|
|
264
|
+
side: ConsoleReviewCommentSide,
|
|
265
|
+
body: string,
|
|
266
|
+
) => {
|
|
267
|
+
if (pjcode === null) {
|
|
268
|
+
throw missingPjcodeError();
|
|
269
|
+
}
|
|
270
|
+
await postConsoleReviewComment(appendToken, {
|
|
271
|
+
pjcode,
|
|
272
|
+
url: prUrl,
|
|
273
|
+
path,
|
|
274
|
+
line,
|
|
275
|
+
side,
|
|
276
|
+
body,
|
|
277
|
+
});
|
|
278
|
+
},
|
|
279
|
+
[pjcode, appendToken],
|
|
280
|
+
);
|
|
281
|
+
|
|
250
282
|
return {
|
|
251
283
|
reviewPullRequest,
|
|
252
284
|
setNextActionDate,
|
|
@@ -255,5 +287,6 @@ export const useConsoleOperations = (
|
|
|
255
287
|
setInTmuxByHuman,
|
|
256
288
|
closeIssue,
|
|
257
289
|
addComment,
|
|
290
|
+
addInlineReviewComment,
|
|
258
291
|
};
|
|
259
292
|
};
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createConsoleApiClient,
|
|
3
|
+
postConsoleOperation,
|
|
4
|
+
postConsoleReviewComment,
|
|
5
|
+
} from './consoleApi';
|
|
2
6
|
|
|
3
7
|
const appendToken = (url: string): string =>
|
|
4
8
|
url.includes('?') ? `${url}&k=token` : `${url}?k=token`;
|
|
@@ -208,3 +212,48 @@ describe('postConsoleOperation', () => {
|
|
|
208
212
|
await expect(failingReview()).rejects.toThrow('HTTP 500');
|
|
209
213
|
});
|
|
210
214
|
});
|
|
215
|
+
|
|
216
|
+
describe('postConsoleReviewComment', () => {
|
|
217
|
+
it('posts the inline review comment body to the reviewcomment endpoint', async () => {
|
|
218
|
+
const fetchMock = mockFetchOnce({ ok: true });
|
|
219
|
+
await postConsoleReviewComment(appendToken, {
|
|
220
|
+
pjcode: 'umino',
|
|
221
|
+
url: 'https://github.com/o/r/pull/1',
|
|
222
|
+
path: 'src/index.ts',
|
|
223
|
+
line: 42,
|
|
224
|
+
side: 'RIGHT',
|
|
225
|
+
body: 'Consider extracting this into a helper.',
|
|
226
|
+
});
|
|
227
|
+
const [url, init] = fetchMock.mock.calls[0];
|
|
228
|
+
expect(url).toBe('/api/reviewcomment?k=token');
|
|
229
|
+
expect(init).toMatchObject({ method: 'POST' });
|
|
230
|
+
expect(JSON.parse((init as { body: string }).body)).toEqual({
|
|
231
|
+
pjcode: 'umino',
|
|
232
|
+
url: 'https://github.com/o/r/pull/1',
|
|
233
|
+
path: 'src/index.ts',
|
|
234
|
+
line: 42,
|
|
235
|
+
side: 'RIGHT',
|
|
236
|
+
body: 'Consider extracting this into a helper.',
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('throws the GitHub error reason surfaced by the server', async () => {
|
|
241
|
+
mockFetchFailureOnce(
|
|
242
|
+
502,
|
|
243
|
+
JSON.stringify({
|
|
244
|
+
error:
|
|
245
|
+
'Failed to create review comment on PR https://github.com/o/r/pull/1: line must be part of the diff',
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
await expect(
|
|
249
|
+
postConsoleReviewComment(appendToken, {
|
|
250
|
+
pjcode: 'umino',
|
|
251
|
+
url: 'https://github.com/o/r/pull/1',
|
|
252
|
+
path: 'src/index.ts',
|
|
253
|
+
line: 42,
|
|
254
|
+
side: 'RIGHT',
|
|
255
|
+
body: 'Consider extracting this into a helper.',
|
|
256
|
+
}),
|
|
257
|
+
).rejects.toThrow('line must be part of the diff');
|
|
258
|
+
});
|
|
259
|
+
});
|
|
@@ -41,6 +41,17 @@ export type ConsoleIntmuxRequest = {
|
|
|
41
41
|
projectItemId: string;
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
+
export type ConsoleReviewCommentSide = 'LEFT' | 'RIGHT';
|
|
45
|
+
|
|
46
|
+
export type ConsoleReviewCommentRequest = {
|
|
47
|
+
pjcode: string;
|
|
48
|
+
url: string;
|
|
49
|
+
path: string;
|
|
50
|
+
line: number;
|
|
51
|
+
side: ConsoleReviewCommentSide;
|
|
52
|
+
body: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
44
55
|
type AppendToken = (url: string) => string;
|
|
45
56
|
|
|
46
57
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
@@ -237,3 +248,19 @@ export const postConsoleComment = async (
|
|
|
237
248
|
}
|
|
238
249
|
return parsePostedComment(await response.json());
|
|
239
250
|
};
|
|
251
|
+
|
|
252
|
+
export const REVIEW_COMMENT_OPERATION_PATH = '/api/reviewcomment';
|
|
253
|
+
|
|
254
|
+
export const postConsoleReviewComment = async (
|
|
255
|
+
appendToken: AppendToken,
|
|
256
|
+
request: ConsoleReviewCommentRequest,
|
|
257
|
+
): Promise<void> => {
|
|
258
|
+
const response = await fetch(appendToken(REVIEW_COMMENT_OPERATION_PATH), {
|
|
259
|
+
method: 'POST',
|
|
260
|
+
headers: { 'Content-Type': 'application/json' },
|
|
261
|
+
body: JSON.stringify(request),
|
|
262
|
+
});
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
throw new Error(await readOperationErrorReason(response));
|
|
265
|
+
}
|
|
266
|
+
};
|
|
@@ -6,6 +6,7 @@ import type { ConsoleCaches } from '../hooks/useConsoleCaches';
|
|
|
6
6
|
import { useConsoleItemDetailData } from '../hooks/useConsoleItemDetailData';
|
|
7
7
|
import type { ConsoleOperationsApi } from '../hooks/useConsoleOperations';
|
|
8
8
|
import { useConsoleToken } from '../hooks/useConsoleToken';
|
|
9
|
+
import type { ConsoleReviewCommentSide } from '../lib/consoleApi';
|
|
9
10
|
import { buildImageProxyUrl } from '../lib/imageProxy';
|
|
10
11
|
import type { ConsoleActionKind } from '../logic/actionToast';
|
|
11
12
|
import { resolveStoryColorEnum } from '../logic/grouping';
|
|
@@ -59,6 +60,15 @@ export const ConsoleItemDetailContainer = ({
|
|
|
59
60
|
[token],
|
|
60
61
|
);
|
|
61
62
|
const hasPullRequest = item.isPr || detail.relatedPullRequests.length > 0;
|
|
63
|
+
const addInlineComment = useCallback(
|
|
64
|
+
(
|
|
65
|
+
path: string,
|
|
66
|
+
line: number,
|
|
67
|
+
side: ConsoleReviewCommentSide,
|
|
68
|
+
body: string,
|
|
69
|
+
) => operations.addInlineReviewComment(item.url, path, line, side, body),
|
|
70
|
+
[operations, item.url],
|
|
71
|
+
);
|
|
62
72
|
|
|
63
73
|
const handlers: ConsoleOperationHandlers = {
|
|
64
74
|
onReview: (action) => {
|
|
@@ -137,6 +147,7 @@ export const ConsoleItemDetailContainer = ({
|
|
|
137
147
|
relatedPullRequests={detail.relatedPullRequests}
|
|
138
148
|
now={now}
|
|
139
149
|
buildImageProxyUrl={resolveImageProxyUrl}
|
|
150
|
+
onAddInlineComment={item.isPr ? addInlineComment : undefined}
|
|
140
151
|
commentComposer={
|
|
141
152
|
<ConsoleCommentComposer
|
|
142
153
|
isPr={item.isPr}
|
|
@@ -826,6 +826,110 @@ body {
|
|
|
826
826
|
font-size: 12px;
|
|
827
827
|
}
|
|
828
828
|
|
|
829
|
+
.console-diff-comment-cell {
|
|
830
|
+
width: 1%;
|
|
831
|
+
padding: 0 2px;
|
|
832
|
+
text-align: center;
|
|
833
|
+
user-select: none;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
.console-diff-comment-button {
|
|
837
|
+
width: 18px;
|
|
838
|
+
height: 18px;
|
|
839
|
+
padding: 0;
|
|
840
|
+
line-height: 16px;
|
|
841
|
+
border: 1px solid transparent;
|
|
842
|
+
border-radius: 4px;
|
|
843
|
+
background: transparent;
|
|
844
|
+
color: #6e7681;
|
|
845
|
+
font-size: 14px;
|
|
846
|
+
cursor: pointer;
|
|
847
|
+
opacity: 0;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.console-diff-row:hover .console-diff-comment-button,
|
|
851
|
+
.console-diff-comment-button[aria-expanded="true"] {
|
|
852
|
+
opacity: 1;
|
|
853
|
+
border-color: #2f81f7;
|
|
854
|
+
background: #1f6feb;
|
|
855
|
+
color: #ffffff;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
.console-diff-composer-row td {
|
|
859
|
+
padding: 6px 10px;
|
|
860
|
+
background: #0d1117;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
.console-diff-composer {
|
|
864
|
+
display: flex;
|
|
865
|
+
flex-direction: column;
|
|
866
|
+
gap: 6px;
|
|
867
|
+
border: 1px solid #30363d;
|
|
868
|
+
border-radius: 6px;
|
|
869
|
+
padding: 8px;
|
|
870
|
+
background: #161b22;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
.console-diff-composer-anchor {
|
|
874
|
+
font-size: 11px;
|
|
875
|
+
color: #8b949e;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.console-diff-composer-input {
|
|
879
|
+
width: 100%;
|
|
880
|
+
resize: vertical;
|
|
881
|
+
border: 1px solid #30363d;
|
|
882
|
+
border-radius: 6px;
|
|
883
|
+
background: #0d1117;
|
|
884
|
+
color: #c9d1d9;
|
|
885
|
+
padding: 6px 8px;
|
|
886
|
+
font: inherit;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
.console-diff-composer-controls {
|
|
890
|
+
display: flex;
|
|
891
|
+
align-items: center;
|
|
892
|
+
gap: 8px;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.console-diff-composer-submit {
|
|
896
|
+
border: 1px solid #238636;
|
|
897
|
+
border-radius: 6px;
|
|
898
|
+
background: #238636;
|
|
899
|
+
color: #ffffff;
|
|
900
|
+
padding: 4px 12px;
|
|
901
|
+
cursor: pointer;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
.console-diff-composer-submit:disabled {
|
|
905
|
+
opacity: 0.6;
|
|
906
|
+
cursor: default;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
.console-diff-composer-cancel {
|
|
910
|
+
border: 1px solid #30363d;
|
|
911
|
+
border-radius: 6px;
|
|
912
|
+
background: transparent;
|
|
913
|
+
color: #c9d1d9;
|
|
914
|
+
padding: 4px 12px;
|
|
915
|
+
cursor: pointer;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.console-diff-composer-status {
|
|
919
|
+
font-size: 12px;
|
|
920
|
+
color: #8b949e;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.console-diff-composer-error {
|
|
924
|
+
color: #ff7b72;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
.console-diff-composer-posted {
|
|
928
|
+
margin: 0;
|
|
929
|
+
font-size: 12px;
|
|
930
|
+
color: #3fb950;
|
|
931
|
+
}
|
|
932
|
+
|
|
829
933
|
.console-pr-header {
|
|
830
934
|
display: flex;
|
|
831
935
|
flex-wrap: wrap;
|