mr_item_model.ts 4.3 KB
Newer Older
1
import * as vscode from 'vscode';
2
import * as assert from 'assert';
3 4 5
import { PROGRAMMATIC_COMMANDS } from '../../command_names';
import { ChangedFileItem } from './changed_file_item';
import { ItemModel } from './item_model';
6
import { GqlDiscussion, GqlTextDiffDiscussion } from '../../gitlab/graphql/get_discussions';
7
import { handleError } from '../../log';
8
import { UserFriendlyError } from '../../errors/user_friendly_error';
9
import { GitLabCommentThread } from '../../review/gitlab_comment_thread';
10
import { CommentingRangeProvider } from '../../review/commenting_range_provider';
11
import { WrappedRepository } from '../../git/wrapped_repository';
12
import { commentControllerProvider } from '../../review/comment_controller_provider';
13 14 15 16 17 18 19
import { GqlTextDiffNote } from '../../gitlab/graphql/shared';
import { toReviewUri } from '../../review/review_uri';
import {
  commentRangeFromPosition,
  commitFromPosition,
  pathFromPosition,
} from '../../review/gql_position_parser';
20

21
const isTextDiffDiscussion = (discussion: GqlDiscussion): discussion is GqlTextDiffDiscussion => {
22 23 24 25
  const firstNote = discussion.notes.nodes[0];
  return firstNote?.position?.positionType === 'text';
};

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
const firstNoteFrom = (discussion: GqlTextDiffDiscussion): GqlTextDiffNote => {
  const note = discussion.notes.nodes[0];
  assert(note, 'discussion should contain at least one note');
  return note;
};

const uriForDiscussion = (
  repository: WrappedRepository,
  mr: RestMr,
  discussion: GqlTextDiffDiscussion,
): vscode.Uri => {
  const { position } = firstNoteFrom(discussion);
  return toReviewUri({
    path: pathFromPosition(position),
    commit: commitFromPosition(position),
    repositoryRoot: repository.rootFsPath,
    projectId: mr.project_id,
    mrId: mr.id,
  });
};

47
export class MrItemModel extends ItemModel {
48
  constructor(readonly mr: RestMr, readonly repository: WrappedRepository) {
49 50 51 52 53 54 55 56 57
    super();
  }

  getTreeItem(): vscode.TreeItem {
    const { iid, title, author } = this.mr;
    const item = new vscode.TreeItem(
      `!${iid} · ${title}`,
      vscode.TreeItemCollapsibleState.Collapsed,
    );
58 59 60
    if (author.avatar_url) {
      item.iconPath = vscode.Uri.parse(author.avatar_url);
    }
61 62 63 64
    return item;
  }

  async getChildren(): Promise<vscode.TreeItem[]> {
65 66 67
    const overview = new vscode.TreeItem('Overview');
    overview.iconPath = new vscode.ThemeIcon('note');
    overview.command = {
68
      command: PROGRAMMATIC_COMMANDS.SHOW_RICH_CONTENT,
69
      arguments: [this.mr, this.repository.rootFsPath],
70
      title: 'Show MR Overview',
71
    };
T
Tomas Vik 已提交
72
    const { mrVersion } = await this.repository.reloadMr(this.mr);
73
    try {
74
      await this.initializeMrDiscussions(mrVersion);
75
    } catch (e) {
76 77
      handleError(
        new UserFriendlyError(
78 79 80
          `The extension failed to preload discussions on the MR diff.
            It's possible that you've encountered
            https://gitlab.com/gitlab-org/gitlab/-/issues/298827.`,
81 82 83
          e,
        ),
      );
84
    }
85 86

    const changedFiles = mrVersion.diffs.map(
87
      d => new ChangedFileItem(this.mr, mrVersion, d, this.repository.rootFsPath),
88
    );
89
    return [overview, ...changedFiles];
90 91
  }

92
  private async initializeMrDiscussions(mrVersion: RestMrVersion): Promise<void> {
93 94 95 96 97
    const gitlabService = this.repository.getGitLabService();
    const userCanComment = await gitlabService.canUserCommentOnMr(this.mr);

    const commentController = commentControllerProvider.borrowCommentController(
      this.mr.references.full,
98
      this.mr.title,
99
      userCanComment ? new CommentingRangeProvider(this.mr, mrVersion) : undefined,
100
    );
101 102
    this.setDisposableChildren([commentController]);

103 104 105
    const discussions = await gitlabService.getDiscussions({
      issuable: this.mr,
    });
106
    const discussionsOnDiff = discussions.filter(isTextDiffDiscussion);
107
    discussionsOnDiff.forEach(discussion => {
108 109 110 111 112 113 114 115 116 117
      const { position } = firstNoteFrom(discussion);
      const vsThread = commentController.createCommentThread(
        uriForDiscussion(this.repository, this.mr, discussion),
        commentRangeFromPosition(position),
        // the comments need to know about the thread, so we first
        // create empty thread to be able to create comments
        [],
      );
      return new GitLabCommentThread(
        vsThread,
118
        discussion,
119 120 121
        this.repository.getGitLabService(),
        this.mr,
      );
122 123
    });
  }
124
}