From d29c36f9d5126ffe7e2d8b40c5b672585315792c Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 22 Oct 2018 14:06:37 -0700 Subject: [PATCH 1/2] Edit/delete comments within the editor --- src/common/comment.ts | 3 + src/github/credentials.ts | 19 ++++- src/github/interface.ts | 2 + src/github/pullRequestManager.ts | 42 +++++++-- src/typings/vscode.proposed.d.ts | 117 ++++++++++++++++++++++++-- src/view/prDocumentCommentProvider.ts | 24 +++++- src/view/reviewManager.ts | 103 +++++++++++++++++++++-- src/view/treeNodes/pullRequestNode.ts | 77 +++++++++++------ 8 files changed, 340 insertions(+), 47 deletions(-) diff --git a/src/common/comment.ts b/src/common/comment.ts index ae3da88742..409608f168 100644 --- a/src/common/comment.ts +++ b/src/common/comment.ts @@ -27,5 +27,8 @@ export interface Comment { created_at: string; updated_at: string; html_url: string; + absolutePosition?: number; + canEdit: boolean; + canDelete: boolean; } diff --git a/src/github/credentials.ts b/src/github/credentials.ts index 05333d3645..db9e1274b0 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -20,6 +20,7 @@ const AUTH_INPUT_TOKEN_CMD = 'auth.inputTokenCallback'; export class CredentialStore { private _octokits: Map; + private _logins: Map; private _configuration: VSCodeConfiguration; private _authenticationStatusBarItems: Map; @@ -27,6 +28,7 @@ export class CredentialStore { private readonly _telemetry: ITelemetry) { this._configuration = configuration; this._octokits = new Map(); + this._logins = new Map(); this._authenticationStatusBarItems = new Map(); vscode.commands.registerCommand(AUTH_INPUT_TOKEN_CMD, async () => { const uriStr = await vscode.window.showInputBox({ prompt: 'Token' }); @@ -71,7 +73,7 @@ export class CredentialStore { if (octokit) { this._octokits.set(host, octokit); } - this.updateAuthenticationStatusBar(remote); + await this.updateAuthenticationStatusBar(remote); return this._octokits.has(host); } @@ -81,6 +83,14 @@ export class CredentialStore { return this._octokits.get(host); } + public getLogin(remote: Remote): string { + return this._logins.get(remote.normalizedHost); + } + + public setLogin(remote: Remote, login: string): void { + this._logins.set(remote.normalizedHost, login); + } + public async loginWithConfirmation(remote: Remote): Promise { const normalizedUri = remote.gitProtocol.normalizeUri(); const result = await vscode.window.showInformationMessage( @@ -145,6 +155,10 @@ export class CredentialStore { return octokit; } + public isCurrentUser(username: string, remote: Remote): boolean { + return username === this.getLogin(remote); + } + private createOctokit(type: string, creds: IHostConfiguration): Octokit { const octokit = new Octokit({ baseUrl: `${HostHelper.getApiHost(creds).toString().slice(0, -1)}${HostHelper.getApiPath(creds, '')}`, @@ -177,8 +191,10 @@ export class CredentialStore { try { const user = await octokit.users.get({}); text = `$(mark-github) ${user.data.login}`; + this.setLogin(remote, user.data.login); } catch (e) { text = '$(mark-github) Signed in'; + this.setLogin(remote, undefined); } command = null; @@ -186,6 +202,7 @@ export class CredentialStore { const authority = remote.gitProtocol.normalizeUri().authority; text = `$(mark-github) Sign in to ${authority}`; command = 'pr.signin'; + this.setLogin(remote, undefined); } statusBarItem.text = text; diff --git a/src/github/interface.ts b/src/github/interface.ts index cd3508fe32..d3fd2ed67a 100644 --- a/src/github/interface.ts +++ b/src/github/interface.ts @@ -178,6 +178,8 @@ export interface IPullRequestManager { createCommentReply(pullRequest: IPullRequestModel, body: string, reply_to: string): Promise; createComment(pullRequest: IPullRequestModel, body: string, path: string, position: number): Promise; mergePullRequest(pullRequest: IPullRequestModel): Promise; + editComment(pullRequest: IPullRequestModel, commentId: string, text: string): Promise; + deleteComment(pullRequest: IPullRequestModel, commentId: string): Promise; closePullRequest(pullRequest: IPullRequestModel): Promise; approvePullRequest(pullRequest: IPullRequestModel, message?: string): Promise; requestChanges(pullRequest: IPullRequestModel, message?: string): Promise; diff --git a/src/github/pullRequestManager.ts b/src/github/pullRequestManager.ts index 64256d2b18..3c31824c64 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -270,7 +270,7 @@ export class PullRequestManager implements IPullRequestManager { number: pullRequest.prNumber, per_page: 100 }); - const rawComments = reviewData.data; + const rawComments = reviewData.data.map(comment => this.addCommentPermissions(comment, remote)); return parserCommentDiffHunk(rawComments); } @@ -316,7 +316,7 @@ export class PullRequestManager implements IPullRequestManager { review_id: reviewId }); - const rawComments = reviewData.data; + const rawComments = reviewData.data.map(comment => this.addCommentPermissions(comment, remote)); return parserCommentDiffHunk(rawComments); } @@ -356,7 +356,7 @@ export class PullRequestManager implements IPullRequestManager { repo: remote.repositoryName }); - return promise.data; + return this.addCommentPermissions(promise.data, remote); } async createCommentReply(pullRequest: IPullRequestModel, body: string, reply_to: string): Promise { @@ -371,7 +371,7 @@ export class PullRequestManager implements IPullRequestManager { in_reply_to: Number(reply_to) }); - return ret.data; + return this.addCommentPermissions(ret.data, remote); } catch (e) { this.handleError(e); } @@ -391,12 +391,44 @@ export class PullRequestManager implements IPullRequestManager { position: position }); - return ret.data; + return this.addCommentPermissions(ret.data, remote); } catch (e) { this.handleError(e); } } + async editComment(pullRequest: IPullRequestModel, commentId: string, text: string): Promise { + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); + + const ret = await octokit.pullRequests.editComment({ + owner: remote.owner, + repo: remote.repositoryName, + body: text, + comment_id: commentId + }); + + return this.addCommentPermissions(ret.data, remote); + } + + async deleteComment(pullRequest: IPullRequestModel, commentId: string): Promise { + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); + + await octokit.pullRequests.deleteComment({ + owner: remote.owner, + repo: remote.repositoryName, + comment_id: commentId + }); + } + + private addCommentPermissions(rawComment: Comment, remote: Remote): Comment { + const isCurrentUser = this._credentialStore.isCurrentUser(rawComment.user.login, remote); + const notOutdated = rawComment.position !== null; + rawComment.canEdit = isCurrentUser && notOutdated; + rawComment.canDelete = isCurrentUser && notOutdated; + + return rawComment; + } + private async changePullRequestState(state: 'open' | 'closed', pullRequest: IPullRequestModel): Promise { const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); diff --git a/src/typings/vscode.proposed.d.ts b/src/typings/vscode.proposed.d.ts index 9b3a074851..415aa81b9c 100644 --- a/src/typings/vscode.proposed.d.ts +++ b/src/typings/vscode.proposed.d.ts @@ -518,7 +518,14 @@ declare module 'vscode' { */ interface CommentInfo { + /** + * All of the comment threads associated with the document. + */ threads: CommentThread[]; + + /** + * The ranges of the document which support commenting. + */ commentingRanges?: Range[]; } @@ -533,19 +540,85 @@ declare module 'vscode' { Expanded = 1 } + /** + * A collection of comments representing a conversation at a particular range in a document. + */ interface CommentThread { + /** + * A unique identifier of the comment thread. + */ threadId: string; + + /** + * The uri of the document the thread has been created on. + */ resource: Uri; + + /** + * The range the comment thread is located within the document. The thread icon will be shown + * at the first line of the range. + */ range: Range; + + /** + * The ordered comments of the thread. + */ comments: Comment[]; + + /** + * Whether the thread should be collapsed or expanded when opening the document. Defaults to Collapsed. + */ collapsibleState?: CommentThreadCollapsibleState; } + /** + * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. + */ interface Comment { + /** + * The id of the comment + */ commentId: string; + + /** + * The text of the comment + */ body: MarkdownString; + + /** + * The display name of the user who created the comment + */ userName: string; - gravatar: string; + + /** + * The icon path for the user who created the comment + */ + userIconPath?: Uri; + + /** + * @deprecated Use userIconPath instead. The avatar src of the user who created the comment + */ + gravatar?: string; + + /** + * Whether the current user has permission to edit the comment. + * + * This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or + * if it is provided by a `DocumentCommentProvider` and no `editComment` method is given. + */ + canEdit?: boolean; + + /** + * Whether the current user has permission to delete the comment. + * + * This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or + * if it is provided by a `DocumentCommentProvider` and no `deleteComment` method is given. + */ + canDelete?: boolean; + + /** + * The command to be executed if the comment is selected in the Comments Panel + */ command?: Command; } @@ -567,18 +640,48 @@ declare module 'vscode' { } interface DocumentCommentProvider { + /** + * Provide the commenting ranges and comment threads for the given document. The comments are displayed within the editor. + */ provideDocumentComments(document: TextDocument, token: CancellationToken): Promise; - createNewCommentThread?(document: TextDocument, range: Range, text: string, token: CancellationToken): Promise; - replyToCommentThread?(document: TextDocument, range: Range, commentThread: CommentThread, text: string, token: CancellationToken): Promise; - onDidChangeCommentThreads?: Event; + + /** + * Called when a user adds a new comment thread in the document at the specified range, with body text. + */ + createNewCommentThread(document: TextDocument, range: Range, text: string, token: CancellationToken): Promise; + + /** + * Called when a user replies to a new comment thread in the document at the specified range, with body text. + */ + replyToCommentThread(document: TextDocument, range: Range, commentThread: CommentThread, text: string, token: CancellationToken): Promise; + + /** + * Called when a user edits the comment body to the be new text text. + */ + editComment?(document: TextDocument, comment: Comment, text: string, token: CancellationToken): Promise; + + /** + * Called when a user deletes the comment. + */ + deleteComment?(document: TextDocument, comment: Comment, token: CancellationToken): Promise; + + /** + * Notify of updates to comment threads. + */ + onDidChangeCommentThreads: Event; } interface WorkspaceCommentProvider { + /** + * Provide all comments for the workspace. Comments are shown within the comments panel. Selecting a comment + * from the panel runs the comment's command. + */ provideWorkspaceComments(token: CancellationToken): Promise; - createNewCommentThread?(document: TextDocument, range: Range, text: string, token: CancellationToken): Promise; - replyToCommentThread?(document: TextDocument, range: Range, commentThread: CommentThread, text: string, token: CancellationToken): Promise; - onDidChangeCommentThreads?: Event; + /** + * Notify of updates to comment threads. + */ + onDidChangeCommentThreads: Event; } namespace workspace { diff --git a/src/view/prDocumentCommentProvider.ts b/src/view/prDocumentCommentProvider.ts index 830ab14065..0750c7e7da 100644 --- a/src/view/prDocumentCommentProvider.ts +++ b/src/view/prDocumentCommentProvider.ts @@ -10,7 +10,7 @@ import { fromPRUri } from '../common/uri'; export class PRDocumentCommentProvider implements vscode.DocumentCommentProvider { private _onDidChangeCommentThreads: vscode.EventEmitter = new vscode.EventEmitter(); - public onDidChangeCommentThreads?: vscode.Event = this._onDidChangeCommentThreads.event; + public onDidChangeCommentThreads: vscode.Event = this._onDidChangeCommentThreads.event; private _prDocumentCommentProviders: {[key: number]: vscode.DocumentCommentProvider} = {}; @@ -64,6 +64,28 @@ export class PRDocumentCommentProvider implements vscode.DocumentCommentProvider return await this._prDocumentCommentProviders[params.prNumber].replyToCommentThread(document, range, commentThread, text, token); } + + async editComment(document: vscode.TextDocument, comment: vscode.Comment, text: string, token: vscode.CancellationToken): Promise { + const params = fromPRUri(document.uri); + const commentProvider = this._prDocumentCommentProviders[params.prNumber]; + + if (!commentProvider) { + throw new Error(`Couldn't find document provider`); + } + + return await commentProvider.editComment(document, comment, text, token); + } + + async deleteComment(document: vscode.TextDocument, comment: vscode.Comment, token: vscode.CancellationToken): Promise { + const params = fromPRUri(document.uri); + const commentProvider = this._prDocumentCommentProviders[params.prNumber]; + + if (!commentProvider) { + throw new Error(`Couldn't find document provider`); + } + + return await commentProvider.deleteComment(document, comment, token); + } } const prDocumentCommentProvider = new PRDocumentCommentProvider(); diff --git a/src/view/reviewManager.ts b/src/view/reviewManager.ts index ed791ae029..cdb96b3b21 100644 --- a/src/view/reviewManager.ts +++ b/src/view/reviewManager.ts @@ -301,7 +301,9 @@ export class ReviewManager implements vscode.DecorationProvider { commentId: comment.id, body: new vscode.MarkdownString(comment.body), userName: comment.user.login, - gravatar: comment.user.avatar_url + gravatar: comment.user.avatar_url, + canEdit: comment.canEdit, + canDelete: comment.canDelete }); matchedFile.comments.push(comment); @@ -342,7 +344,9 @@ export class ReviewManager implements vscode.DecorationProvider { commentId: rawComment.id, body: new vscode.MarkdownString(rawComment.body), userName: rawComment.user.login, - gravatar: rawComment.user.avatar_url + gravatar: rawComment.user.avatar_url, + canEdit: rawComment.canEdit, + canDelete: rawComment.canDelete }; let commentThread: vscode.CommentThread = { @@ -368,6 +372,82 @@ export class ReviewManager implements vscode.DecorationProvider { } } + private async editComment(document: vscode.TextDocument, comment: vscode.Comment, text: string): Promise { + try { + const matchedFile = this.findMatchedFileByUri(document); + if (!matchedFile) { + throw new Error('Unable to find matching file'); + } + + const editedComment = await this._prManager.editComment(this._prManager.activePullRequest, comment.commentId, text); + + // Update the cached comments of the file + const matchingCommentIndex = matchedFile.comments.findIndex(c => c.id === comment.commentId); + if (matchingCommentIndex > -1) { + matchedFile.comments.splice(matchingCommentIndex, 1, editedComment); + const changedThreads = this.fileCommentsToCommentThreads(matchedFile, matchedFile.comments.filter(c => c.position === editedComment.position), vscode.CommentThreadCollapsibleState.Expanded); + + this._onDidChangeWorkspaceCommentThreads.fire({ + added: [], + changed: changedThreads, + removed: [] + }); + } + + // Also update this._comments + const indexInAllComments = this._comments.findIndex(c => c.id === comment.commentId); + if (indexInAllComments > -1) { + this._comments.splice(indexInAllComments, 1, editedComment); + } + } catch (e) { + throw new Error(formatError(e)); + } + } + + private async deleteComment(document: vscode.TextDocument, comment: vscode.Comment): Promise { + try { + const matchedFile = this.findMatchedFileByUri(document); + if (!matchedFile) { + throw new Error('Unable to find matching file'); + } + + await this._prManager.deleteComment(this._prManager.activePullRequest, comment.commentId); + const matchingCommentIndex = matchedFile.comments.findIndex(c => c.id === comment.commentId); + if (matchingCommentIndex > -1) { + const [ deletedComment ] = matchedFile.comments.splice(matchingCommentIndex, 1); + const updatedThreadComments = matchedFile.comments.filter(c => c.position === deletedComment.position); + + // If the deleted comment was the last in its thread, remove the thread + if (updatedThreadComments.length) { + const changedThreads = this.fileCommentsToCommentThreads(matchedFile, updatedThreadComments, vscode.CommentThreadCollapsibleState.Expanded); + this._onDidChangeWorkspaceCommentThreads.fire({ + added: [], + changed: changedThreads, + removed: [] + }); + } else { + this._onDidChangeWorkspaceCommentThreads.fire({ + added: [], + changed: [], + removed: [{ + threadId: deletedComment.id, + resource: vscode.Uri.file(nodePath.resolve(this._repository.rootUri.fsPath, deletedComment.path)), + comments: [], + range: null + }] + }); + } + } + + const indexInAllComments = this._comments.findIndex(c => c.id === comment.commentId); + if (indexInAllComments > -1) { + this._comments.splice(indexInAllComments, 1); + } + } catch (e) { + throw new Error(formatError(e)); + } + } + private async updateComments(): Promise { const branch = this._repository.state.HEAD; if (!branch) { return; } @@ -596,7 +676,9 @@ export class ReviewManager implements vscode.DecorationProvider { arguments: [ fileChange ] - } + }, + canEdit: comment.canEdit, + canDelete: comment.canDelete }; }), collapsibleState: collapsibleState @@ -646,7 +728,9 @@ export class ReviewManager implements vscode.DecorationProvider { body: new vscode.MarkdownString(comment.body), userName: comment.user.login, gravatar: comment.user.avatar_url, - command: command + command: command, + canEdit: comment.canEdit, + canDelete: comment.canDelete }; }), collapsibleState: collapsibleState @@ -831,7 +915,9 @@ export class ReviewManager implements vscode.DecorationProvider { commentId: comment.id, body: new vscode.MarkdownString(comment.body), userName: comment.user.login, - gravatar: comment.user.avatar_url + gravatar: comment.user.avatar_url, + canEdit: comment.canEdit, + canDelete: comment.canDelete }; }), collapsibleState: vscode.CommentThreadCollapsibleState.Expanded @@ -844,7 +930,9 @@ export class ReviewManager implements vscode.DecorationProvider { } }, createNewCommentThread: this.createNewCommentThread.bind(this), - replyToCommentThread: this.replyToCommentThread.bind(this) + replyToCommentThread: this.replyToCommentThread.bind(this), + editComment: this.editComment.bind(this), + deleteComment: this.deleteComment.bind(this) }); this._workspaceCommentProvider = vscode.workspace.registerWorkspaceCommentProvider({ @@ -857,8 +945,7 @@ export class ReviewManager implements vscode.DecorationProvider { return this.outdatedCommentsToCommentThreads(fileChange, fileChange.comments, vscode.CommentThreadCollapsibleState.Expanded); }); return [...comments, ...outdatedComments].reduce((prev, curr) => prev.concat(curr), []); - }, - createNewCommentThread: this.createNewCommentThread.bind(this), replyToCommentThread: this.replyToCommentThread.bind(this) + } }); } diff --git a/src/view/treeNodes/pullRequestNode.ts b/src/view/treeNodes/pullRequestNode.ts index 0b571eac45..94004f710f 100644 --- a/src/view/treeNodes/pullRequestNode.ts +++ b/src/view/treeNodes/pullRequestNode.ts @@ -99,7 +99,9 @@ export function providePRDocumentComments( commentId: comment.id, body: new vscode.MarkdownString(comment.body), userName: comment.user.login, - gravatar: comment.user.avatar_url + gravatar: comment.user.avatar_url, + canEdit: comment.canEdit, + canDelete: comment.canDelete }; }), collapsibleState: vscode.CommentThreadCollapsibleState.Expanded, @@ -140,7 +142,9 @@ function commentsToCommentThreads(fileChange: InMemFileChangeNode, comments: Com commentId: comment.id, body: new vscode.MarkdownString(comment.body), userName: comment.user.login, - gravatar: comment.user.avatar_url + gravatar: comment.user.avatar_url, + canEdit: comment.canEdit, + canDelete: comment.canDelete }; }), collapsibleState: vscode.CommentThreadCollapsibleState.Expanded, @@ -278,7 +282,9 @@ export class PRNode extends TreeNode { onDidChangeCommentThreads: this._onDidChangeCommentThreads.event, provideDocumentComments: this.provideDocumentComments.bind(this), createNewCommentThread: this.createNewCommentThread.bind(this), - replyToCommentThread: this.replyToCommentThread.bind(this) + replyToCommentThread: this.replyToCommentThread.bind(this), + editComment: this.editComment.bind(this), + deleteComment: this.deleteComment.bind(this) }); } } else { @@ -434,6 +440,21 @@ export class PRNode extends TreeNode { return ''; } + private findMatchingFileNode(uri: vscode.Uri): InMemFileChangeNode { + const params = fromPRUri(uri); + const fileChange = this._fileChanges.find(change => change.fileName === params.fileName); + + if (!fileChange) { + throw new Error('No matching file found'); + } + + if (fileChange instanceof RemoteFileChangeNode) { + throw new Error('Comments not supported on remote file changes'); + } + + return fileChange; + } + private async createNewCommentThread(document: vscode.TextDocument, range: vscode.Range, text: string) { try { let uri = document.uri; @@ -443,15 +464,7 @@ export class PRNode extends TreeNode { return null; } - let fileChange = this._fileChanges.find(change => change.fileName === params.fileName); - - if (!fileChange) { - throw new Error('No matching file found'); - } - - if (fileChange instanceof RemoteFileChangeNode) { - throw new Error('Cannot add comment to this file'); - } + const fileChange = this.findMatchingFileNode(uri); let isBase = params && params.isBase; let position = mapHeadLineToDiffHunkPosition(fileChange.diffHunks, '', range.start.line + 1, isBase); @@ -466,7 +479,9 @@ export class PRNode extends TreeNode { commentId: rawComment.id, body: new vscode.MarkdownString(rawComment.body), userName: rawComment.user.login, - gravatar: rawComment.user.avatar_url + gravatar: rawComment.user.avatar_url, + canEdit: rawComment.canEdit, + canDelete: rawComment.canDelete }; fileChange.comments.push(rawComment); @@ -484,26 +499,38 @@ export class PRNode extends TreeNode { } } - private async replyToCommentThread(document: vscode.TextDocument, _range: vscode.Range, thread: vscode.CommentThread, text: string) { - try { - const uri = document.uri; - const params = fromPRUri(uri); - const fileChange = this._fileChanges.find(change => change.fileName === params.fileName); + private async editComment(document: vscode.TextDocument, comment: vscode.Comment, text: string): Promise { + const fileChange = this.findMatchingFileNode(document.uri); + const rawComment = await this._prManager.editComment(this.pullRequestModel, comment.commentId, text); - if (!fileChange) { - throw new Error('No matching file found'); - } + const index = fileChange.comments.findIndex(c => c.id === comment.commentId); + if (index > -1) { + fileChange.comments.splice(index, 1, rawComment); + } + } - if (fileChange instanceof RemoteFileChangeNode) { - throw new Error('Cannot add comment to this file'); - } + private async deleteComment(document: vscode.TextDocument, comment: vscode.Comment): Promise { + const fileChange = this.findMatchingFileNode(document.uri); + + await this._prManager.deleteComment(this.pullRequestModel, comment.commentId); + const index = fileChange.comments.findIndex(c => c.id === comment.commentId); + if (index > -1) { + fileChange.comments.splice(index, 1); + } + } + + private async replyToCommentThread(document: vscode.TextDocument, _range: vscode.Range, thread: vscode.CommentThread, text: string) { + try { + const fileChange = this.findMatchingFileNode(document.uri); const rawComment = await this._prManager.createCommentReply(this.pullRequestModel, text, thread.threadId); thread.comments.push({ commentId: rawComment.id, body: new vscode.MarkdownString(rawComment.body), userName: rawComment.user.login, - gravatar: rawComment.user.avatar_url + gravatar: rawComment.user.avatar_url, + canEdit: rawComment.canEdit, + canDelete: rawComment.canDelete }); fileChange.comments.push(rawComment); From 78ca42e747bfa0ef8d36c8665efae936239c60f2 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Thu, 25 Oct 2018 17:07:14 -0700 Subject: [PATCH 2/2] Use octokit to store current user, format errors --- src/github/credentials.ts | 17 +++------------ src/github/pullRequestManager.ts | 36 +++++++++++++++++++------------- 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/github/credentials.ts b/src/github/credentials.ts index db9e1274b0..53812257b0 100644 --- a/src/github/credentials.ts +++ b/src/github/credentials.ts @@ -20,7 +20,6 @@ const AUTH_INPUT_TOKEN_CMD = 'auth.inputTokenCallback'; export class CredentialStore { private _octokits: Map; - private _logins: Map; private _configuration: VSCodeConfiguration; private _authenticationStatusBarItems: Map; @@ -28,7 +27,6 @@ export class CredentialStore { private readonly _telemetry: ITelemetry) { this._configuration = configuration; this._octokits = new Map(); - this._logins = new Map(); this._authenticationStatusBarItems = new Map(); vscode.commands.registerCommand(AUTH_INPUT_TOKEN_CMD, async () => { const uriStr = await vscode.window.showInputBox({ prompt: 'Token' }); @@ -83,14 +81,6 @@ export class CredentialStore { return this._octokits.get(host); } - public getLogin(remote: Remote): string { - return this._logins.get(remote.normalizedHost); - } - - public setLogin(remote: Remote, login: string): void { - this._logins.set(remote.normalizedHost, login); - } - public async loginWithConfirmation(remote: Remote): Promise { const normalizedUri = remote.gitProtocol.normalizeUri(); const result = await vscode.window.showInformationMessage( @@ -156,7 +146,8 @@ export class CredentialStore { } public isCurrentUser(username: string, remote: Remote): boolean { - return username === this.getLogin(remote); + const octokit = this.getOctokit(remote); + return octokit && (octokit as any).currentUser && (octokit as any).currentUser.login === username; } private createOctokit(type: string, creds: IHostConfiguration): Octokit { @@ -190,11 +181,10 @@ export class CredentialStore { if (octokit) { try { const user = await octokit.users.get({}); + (octokit as any).currentUser = user.data; text = `$(mark-github) ${user.data.login}`; - this.setLogin(remote, user.data.login); } catch (e) { text = '$(mark-github) Signed in'; - this.setLogin(remote, undefined); } command = null; @@ -202,7 +192,6 @@ export class CredentialStore { const authority = remote.gitProtocol.normalizeUri().authority; text = `$(mark-github) Sign in to ${authority}`; command = 'pr.signin'; - this.setLogin(remote, undefined); } statusBarItem.text = text; diff --git a/src/github/pullRequestManager.ts b/src/github/pullRequestManager.ts index 3c31824c64..4df4c60e71 100644 --- a/src/github/pullRequestManager.ts +++ b/src/github/pullRequestManager.ts @@ -398,26 +398,34 @@ export class PullRequestManager implements IPullRequestManager { } async editComment(pullRequest: IPullRequestModel, commentId: string, text: string): Promise { - const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); + try { + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); - const ret = await octokit.pullRequests.editComment({ - owner: remote.owner, - repo: remote.repositoryName, - body: text, - comment_id: commentId - }); + const ret = await octokit.pullRequests.editComment({ + owner: remote.owner, + repo: remote.repositoryName, + body: text, + comment_id: commentId + }); - return this.addCommentPermissions(ret.data, remote); + return this.addCommentPermissions(ret.data, remote); + } catch (e) { + throw new Error(formatError(e)); + } } async deleteComment(pullRequest: IPullRequestModel, commentId: string): Promise { - const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); + try { + const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure(); - await octokit.pullRequests.deleteComment({ - owner: remote.owner, - repo: remote.repositoryName, - comment_id: commentId - }); + await octokit.pullRequests.deleteComment({ + owner: remote.owner, + repo: remote.repositoryName, + comment_id: commentId + }); + } catch (e) { + throw new Error(formatError(e)); + } } private addCommentPermissions(rawComment: Comment, remote: Remote): Comment {