src/desktop/commands/mr_discussion_commands.ts (117 lines of code) (raw):

import assert from 'assert'; import * as vscode from 'vscode'; import { FAILED_COMMENT_CONTEXT } from '../constants'; import { getNewLineForOldUnchangedLine } from '../git/diff_line_count'; import { getGitLabService } from '../gitlab/get_gitlab_service'; import { getProjectRepository } from '../gitlab/gitlab_project_repository'; import { mrCache } from '../gitlab/mr_cache'; import { GitLabComment } from '../review/gitlab_comment'; import { GitLabCommentThread } from '../review/gitlab_comment_thread'; import { fromReviewUri } from '../review/review_uri'; import { findFileInDiffs } from '../utils/find_file_in_diffs'; const getLineNumber = (thread: vscode.CommentThread) => thread.range.start.line + 1; const createNewComment = async ( text: string, thread: vscode.CommentThread, ): Promise<GitLabCommentThread> => { const { path, commit, repositoryRoot, mrId } = fromReviewUri(thread.uri); const projectInRepository = getProjectRepository().getProjectOrFail(repositoryRoot); const cachedMr = mrCache.getMr(mrId, projectInRepository); assert(cachedMr); const { mr, mrVersion } = cachedMr; const isOld = commit === mrVersion.base_commit_sha; const diff = findFileInDiffs(mrVersion.diffs, isOld ? { oldPath: path } : { newPath: path }); assert(diff); if (diff.diff === '') throw new Error( 'Error: Creating comment failed. The merge request diff for this file is too large. ' + 'Please see https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/578', ); const positionFragment = isOld ? { oldLine: getLineNumber(thread), // we let user comment on any line on the old version of the diff // this means some of the lines might be unchanged // till https://gitlab.com/gitlab-org/gitlab/-/issues/325161 gets fixed, we need to compute // the new line index for unchanged line. newLine: getNewLineForOldUnchangedLine(mrVersion, path, getLineNumber(thread)), } : { newLine: getLineNumber(thread) }; const discussion = await getGitLabService(projectInRepository).createDiffNote(mrId, text, { baseSha: mrVersion.base_commit_sha, headSha: mrVersion.head_commit_sha, startSha: mrVersion.start_commit_sha, paths: { oldPath: diff.old_path, newPath: diff.new_path, }, ...positionFragment, }); return new GitLabCommentThread(thread, discussion, getGitLabService(projectInRepository), mr); }; export interface CommentWithThread extends vscode.Comment { thread: vscode.CommentThread; } const createFailedComment = (body: string, thread: vscode.CommentThread): CommentWithThread => ({ author: { name: '' }, // we don't want to show author name for failed comment body, mode: vscode.CommentMode.Editing, contextValue: FAILED_COMMENT_CONTEXT, thread, }); const addFailedCommentToThread = (text: string, vsThread: vscode.CommentThread): void => { vsThread.comments = [createFailedComment(text, vsThread)]; // eslint-disable-line no-param-reassign vsThread.canReply = false; // eslint-disable-line no-param-reassign }; export const toggleResolved = async (vsThread: vscode.CommentThread): Promise<void> => { const firstComment = vsThread.comments[0]; assert(firstComment instanceof GitLabComment); const gitlabThread = firstComment.thread; return gitlabThread.toggleResolved(); }; export const deleteComment = async (comment: GitLabComment): Promise<void> => { const DELETE_ACTION = 'Delete'; const shouldDelete = await vscode.window.showWarningMessage( 'Delete comment?', { modal: true }, DELETE_ACTION, ); if (shouldDelete !== DELETE_ACTION) { return undefined; } return comment.thread.deleteComment(comment); }; export const editComment = (comment: GitLabComment): void => { comment.thread.startEdit(comment); }; export const cancelEdit = (comment: GitLabComment): void => { comment.thread.cancelEdit(comment); }; export const cancelFailedComment = (comment: CommentWithThread): void => { const { thread } = comment; thread.dispose(); }; export const submitEdit = async (comment: GitLabComment): Promise<void> => comment.thread.submitEdit(comment); export const createComment = async ({ text, thread, }: { text: string; thread: vscode.CommentThread; }): Promise<void> => { const firstComment = thread.comments[0]; if (!firstComment || firstComment.contextValue?.match(FAILED_COMMENT_CONTEXT)) { try { await createNewComment(text, thread); return; } catch (e) { addFailedCommentToThread(text, thread); throw e; } } assert(firstComment instanceof GitLabComment); const gitlabThread = firstComment.thread; await gitlabThread.reply(text); }; export const retryFailedComment = async (comment: CommentWithThread): Promise<void> => { const { thread } = comment; const text = comment.body as string; return createComment({ text, thread }); };