| 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 | |
| 8 | import {useAtomValue} from 'jotai'; |
| 9 | import clientToServerAPI from '../ClientToServerAPI'; |
| 10 | import {T} from '../i18n'; |
| 11 | import {writeAtom} from '../jotaiUtils'; |
| 12 | |
| 13 | import {ErrorNotice} from 'isl-components/ErrorNotice'; |
| 14 | import {Icon} from 'isl-components/Icon'; |
| 15 | import {minimalDisambiguousPaths} from 'shared/minimalDisambiguousPaths'; |
| 16 | import {tracker} from '../analytics'; |
| 17 | import {Collapsable} from '../Collapsable'; |
| 18 | import {relativePath} from '../CwdSelector'; |
| 19 | import {Link} from '../Link'; |
| 20 | import platform from '../platform'; |
| 21 | import {repoRelativeCwd} from '../repositoryData'; |
| 22 | import {registerDisposable} from '../utils'; |
| 23 | import './AICodeReviewStatus.css'; |
| 24 | import { |
| 25 | codeReviewStatusAtom, |
| 26 | commentsByFilePathAtom, |
| 27 | firstPassCommentData, |
| 28 | firstPassCommentDataCount, |
| 29 | firstPassCommentError, |
| 30 | } from './firstPassCodeReviewAtoms'; |
| 31 | |
| 32 | registerDisposable( |
| 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 | |
| 48 | export 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 | |