4.3 KB123 lines
Blame
1/**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8import {useAtomValue} from 'jotai';
9import clientToServerAPI from '../ClientToServerAPI';
10import {T} from '../i18n';
11import {writeAtom} from '../jotaiUtils';
12
13import {ErrorNotice} from 'isl-components/ErrorNotice';
14import {Icon} from 'isl-components/Icon';
15import {minimalDisambiguousPaths} from 'shared/minimalDisambiguousPaths';
16import {tracker} from '../analytics';
17import {Collapsable} from '../Collapsable';
18import {relativePath} from '../CwdSelector';
19import {Link} from '../Link';
20import platform from '../platform';
21import {repoRelativeCwd} from '../repositoryData';
22import {registerDisposable} from '../utils';
23import './AICodeReviewStatus.css';
24import {
25 codeReviewStatusAtom,
26 commentsByFilePathAtom,
27 firstPassCommentData,
28 firstPassCommentDataCount,
29 firstPassCommentError,
30} from './firstPassCodeReviewAtoms';
31
32registerDisposable(
33 firstPassCommentData,
34 clientToServerAPI.onMessageOfType('platform/gotAIReviewComments', data => {
35 const result = data.comments;
36 if (result.error) {
37 writeAtom(codeReviewStatusAtom, 'error');
38 writeAtom(firstPassCommentError, result.error);
39 tracker.error('AICodeReviewCompleted', 'AICodeReviewError', result.error);
40 } else {
41 writeAtom(codeReviewStatusAtom, 'success');
42 tracker.track('AICodeReviewCompleted', {extras: {commentCount: result.value.length}});
43 }
44 }),
45 import.meta.hot,
46);
47
48export function AICodeReviewStatus(): JSX.Element | null {
49 const repoRoot = useAtomValue(repoRelativeCwd);
50 const status = useAtomValue(codeReviewStatusAtom);
51 const commentCount = useAtomValue(firstPassCommentDataCount);
52 const commentsByFilePath = useAtomValue(commentsByFilePathAtom);
53 const error = useAtomValue(firstPassCommentError);
54 const disambiguatedPaths = minimalDisambiguousPaths(Object.keys(commentsByFilePath));
55
56 // TODO: move this component to vscode/webview
57 if (platform.platformName !== 'vscode' || status == null) {
58 return null;
59 }
60
61 return (
62 <Collapsable
63 className="comment-collapsable"
64 title={
65 <div className="comment-collapsible-title">
66 <b>
67 <T>Devmate Code Review</T>
68 </b>
69 <div className="comment-collapsible-title-status">
70 {status === 'success' &&
71 (commentCount > 0 ? (
72 <div className="comment-count">
73 {commentCount}
74 <Icon icon="comment" />
75 </div>
76 ) : (
77 <Icon icon="check" />
78 ))}
79 {status === 'error' && <Icon icon="error" color="red" />}
80 {status === 'running' && <Icon icon="loading" />}
81 </div>
82 </div>
83 }>
84 <div className="comment-content-container">
85 {status === 'running' && (
86 <div className="comment-loading">Devmate is reviewing your code...</div>
87 )}
88 {status === 'success' &&
89 (commentCount > 0 ? (
90 <div className="comment-list">
91 {Object.entries(commentsByFilePath).map(([filepath, comments], i) =>
92 comments.map((comment, j) => (
93 <div className="comment-container" key={comment.issueID || `${filepath}-${j}`}>
94 <div className="comment-header">
95 <Link
96 onClick={() =>
97 platform.openFile(relativePath(repoRoot, filepath), {
98 line: comment.startLine,
99 })
100 }>
101 <b>
102 {disambiguatedPaths[i]}:{comment.startLine}
103 </b>
104 </Link>
105 </div>
106 <div className="comment-body">
107 <T>{comment.description}</T>
108 </div>
109 </div>
110 )),
111 )}
112 </div>
113 ) : (
114 <div>
115 <T>Everything looks good! Devmate didn't find any issues.</T>
116 </div>
117 ))}
118 {status === 'error' && <ErrorNotice title="Failed to load comments" error={error} />}
119 </div>
120 </Collapsable>
121 );
122}
123