From ba2530c4cecdac6114bb8ecda48d093ed9276ece Mon Sep 17 00:00:00 2001 From: mohamedamara1 Date: Sun, 10 Mar 2024 15:48:16 +0100 Subject: [PATCH 1/4] added draft code of linking PR to issue from PR Overview page the issues are listed for now --- src/github/interface.ts | 1 + src/github/pullRequestModel.ts | 5 +++- src/github/pullRequestOverview.ts | 1 + src/github/queriesExtra.gql | 9 +++++++ src/github/views.ts | 2 ++ webviews/components/sidebar.tsx | 39 ++++++++++++++++++++++++++++++- 6 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/github/interface.ts b/src/github/interface.ts index 8f0f2582d0..14a7a1776e 100644 --- a/src/github/interface.ts +++ b/src/github/interface.ts @@ -192,6 +192,7 @@ export interface PullRequest extends Issue { mergeCommitMeta?: { title: string, description: string }; squashCommitMeta?: { title: string, description: string }; suggestedReviewers?: ISuggestedReviewer[]; + closingIssuesReferences?: Pick[] } export interface IRawFileChange { diff --git a/src/github/pullRequestModel.ts b/src/github/pullRequestModel.ts index b3f31033fb..8a485bbc03 100644 --- a/src/github/pullRequestModel.ts +++ b/src/github/pullRequestModel.ts @@ -53,6 +53,7 @@ import { GithubItemStateEnum, IAccount, IRawFileChange, + Issue, ISuggestedReviewer, ITeam, MergeMethod, @@ -115,6 +116,8 @@ export class PullRequestModel extends IssueModel implements IPullRe public mergeQueueEntry?: MergeQueueEntry; public suggestedReviewers?: ISuggestedReviewer[]; public hasChangesSinceLastReview?: boolean; + public issues: Partial[]; + private _showChangesSinceReview: boolean; private _hasPendingReview: boolean = false; private _onDidChangePendingReviewState: vscode.EventEmitter = new vscode.EventEmitter(); @@ -137,7 +140,6 @@ export class PullRequestModel extends IssueModel implements IPullRe private _comments: readonly IComment[] | undefined; private _onDidChangeComments: vscode.EventEmitter = new vscode.EventEmitter(); public readonly onDidChangeComments: vscode.Event = this._onDidChangeComments.event; - // Whether the pull request is currently checked out locally private _isActive: boolean; public get isActive(): boolean { @@ -245,6 +247,7 @@ export class PullRequestModel extends IssueModel implements IPullRe super.update(item); this.isDraft = item.isDraft; this.suggestedReviewers = item.suggestedReviewers; + this.issues = item.closingIssuesReferences ?? []; if (item.isRemoteHeadDeleted != null) { this.isRemoteHeadDeleted = item.isRemoteHeadDeleted; diff --git a/src/github/pullRequestOverview.ts b/src/github/pullRequestOverview.ts index 68a5a4c8a3..29bbba31bc 100644 --- a/src/github/pullRequestOverview.ts +++ b/src/github/pullRequestOverview.ts @@ -281,6 +281,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel[]; repositoryDefaultBranch: string; /** * User can edit PR title and description (author or user with push access) diff --git a/webviews/components/sidebar.tsx b/webviews/components/sidebar.tsx index 235954b9f8..2f29798994 100644 --- a/webviews/components/sidebar.tsx +++ b/webviews/components/sidebar.tsx @@ -13,7 +13,7 @@ import { AuthorLink, Avatar } from '../components/user'; import { closeIcon, settingsIcon } from './icon'; import { Reviewer } from './reviewer'; -export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue, projectItems: projects, milestone, assignees }: PullRequest) { +export default function Sidebar({ reviewers, labels, issues, hasWritePermission, isIssue, projectItems: projects, milestone, assignees }: PullRequest) { const { addReviewers, addAssignees, @@ -177,6 +177,30 @@ export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue
No milestone
)} +
+
{ + const newMilestone = await addMilestone(); + updatePR({ milestone: newMilestone.added }); + }}> +
Linked issues
+ {hasWritePermission ? ( + + ) : null} +
+ {issues.length ? ( +
+ {issues.map(issue => ( + + ))} +
+ ) : ( +
None yet
+ )} +
); } @@ -248,3 +272,16 @@ function Project(project: IProjectItem & { canDelete: boolean }) { ); } + +function Issue(issue: any) { + const { updatePR, pr } = useContext(PullRequestContext); + + return ( +
+
+

{issue.title}

+ {/* Add more details about the issue as needed */} +
+
+ ); +} From c2e6056a4321457412ccd8ab9f5ffa44584db611 Mon Sep 17 00:00:00 2001 From: mohamedamara1 Date: Sun, 10 Mar 2024 15:51:11 +0100 Subject: [PATCH 2/4] fixed pullRequest builder missing issues field --- webviews/editorWebview/test/builder/pullRequest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/webviews/editorWebview/test/builder/pullRequest.ts b/webviews/editorWebview/test/builder/pullRequest.ts index 40168b7e00..776f58c9ca 100644 --- a/webviews/editorWebview/test/builder/pullRequest.ts +++ b/webviews/editorWebview/test/builder/pullRequest.ts @@ -57,4 +57,5 @@ export const PullRequestBuilder = createBuilderClass()({ hasReviewDraft: { default: false }, busy: { default: undefined }, lastReviewType: { default: undefined }, + issues: { default: [] }, }); From 43fabf3d13c2d15bd6ccb21613a6c25bbafa898e Mon Sep 17 00:00:00 2001 From: mohamedamara1 Date: Sun, 10 Mar 2024 16:37:08 +0100 Subject: [PATCH 3/4] continuation of previous commit, issues linking code --- src/github/graphql.ts | 9 +++++++++ src/github/utils.ts | 30 ++++++++++++++++++++++++++++++ webviews/components/sidebar.tsx | 2 -- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/github/graphql.ts b/src/github/graphql.ts index f77186be5c..8cc2d7cd68 100644 --- a/src/github/graphql.ts +++ b/src/github/graphql.ts @@ -580,6 +580,15 @@ export interface PullRequest { avatarUrl: string; id: string; }; + closingIssuesReferences?: { + nodes: { + id: string; + databaseId: number; + number: number; + title: string; + state: 'OPEN' | 'CLOSED' | 'MERGED'; + }[]; + }; commits: { nodes: { commit: { diff --git a/src/github/utils.ts b/src/github/utils.ts index c28ec49433..da4083efe8 100644 --- a/src/github/utils.ts +++ b/src/github/utils.ts @@ -714,6 +714,7 @@ export function parseGraphQLPullRequest( milestone: parseMilestone(graphQLPullRequest.milestone), assignees: graphQLPullRequest.assignees?.nodes.map(assignee => parseAuthor(assignee, githubRepository)), commits: parseCommits(graphQLPullRequest.commits.nodes), + closingIssuesReferences: parseIssues(graphQLPullRequest.closingIssuesReferences?.nodes), }; pr.mergeCommitMeta = parseCommitMeta(graphQLPullRequest.baseRepository.mergeCommitTitle, graphQLPullRequest.baseRepository.mergeCommitMessage, pr); pr.squashCommitMeta = parseCommitMeta(graphQLPullRequest.baseRepository.squashMergeCommitTitle, graphQLPullRequest.baseRepository.squashMergeCommitMessage, pr); @@ -797,6 +798,35 @@ function parseComments(comments: GraphQL.AbbreviatedIssueComment[] | undefined, return parsedComments; } +function parseIssues(issues: Pick[] | undefined): { + id: number; + number: number; + title: string; + state: 'OPEN' | 'CLOSED' | 'MERGED'; +}[] | undefined { + if (!issues) { + return undefined; + } + + const parsedIssues: { + id: number; + number: number; + title: string; + state: 'OPEN' | 'CLOSED' | 'MERGED'; + }[] = []; + + for (const issue of issues) { + parsedIssues.push({ + id: issue.databaseId, + number: issue.number, + title: issue.title, + state: issue.state, + }); + } + + return parsedIssues; +} + export function parseGraphQLIssue(issue: GraphQL.PullRequest, githubRepository: GitHubRepository): Issue { return { id: issue.databaseId, diff --git a/webviews/components/sidebar.tsx b/webviews/components/sidebar.tsx index 2f29798994..536f01f707 100644 --- a/webviews/components/sidebar.tsx +++ b/webviews/components/sidebar.tsx @@ -274,8 +274,6 @@ function Project(project: IProjectItem & { canDelete: boolean }) { } function Issue(issue: any) { - const { updatePR, pr } = useContext(PullRequestContext); - return (
From a9de53e565da013b67deec1de137b5cf3c8457e2 Mon Sep 17 00:00:00 2001 From: mohamedamara1 Date: Wed, 13 Mar 2024 19:45:51 +0100 Subject: [PATCH 4/4] sync with main --- package.json | 5 +++-- ...de.proposed.commentThreadApplicability.d.ts | 18 ++++++++++++++++++ src/commands.ts | 2 +- src/common/diffHunk.ts | 4 ++-- src/github/interface.ts | 2 +- src/github/issueOverview.ts | 2 +- src/github/prComment.ts | 2 +- src/github/utils.ts | 14 ++++++++++---- src/test/view/reviewCommentController.test.ts | 2 +- src/view/gitHubContentProvider.ts | 2 +- src/view/reviewManager.ts | 11 ++--------- webviews/components/merge.tsx | 2 +- 12 files changed, 42 insertions(+), 24 deletions(-) create mode 100644 src/@types/vscode.proposed.commentThreadApplicability.d.ts diff --git a/package.json b/package.json index 44a477cda8..51efe28ac7 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "enabledApiProposals": [ "activeComment", "commentingRangeHint", + "commentThreadApplicability", "contribCommentsViewThreadMenus", "tokenInformation", "contribShareMenu", @@ -29,7 +30,7 @@ "tabInputTextMerge", "treeViewMarkdownMessage" ], - "version": "0.82.0", + "version": "0.84.0", "publisher": "GitHub", "engines": { "vscode": "^1.88.0" @@ -983,7 +984,7 @@ "command": "pr.unresolveReviewThread", "title": "%command.pr.unresolveReviewThread.title%", "category": "%command.pull.request.category%", - "icon": "$(circle-slash)" + "icon": "$(discard)" }, { "command": "pr.diffOutdatedCommentWithHead", diff --git a/src/@types/vscode.proposed.commentThreadApplicability.d.ts b/src/@types/vscode.proposed.commentThreadApplicability.d.ts new file mode 100644 index 0000000000..878dd35ff7 --- /dev/null +++ b/src/@types/vscode.proposed.commentThreadApplicability.d.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // @alexr00 https://github.com/microsoft/vscode/issues/207402 + + export enum CommentThreadApplicability { + Current = 0, + Outdated = 1 + } + + export interface CommentThread2 { + state?: CommentThreadState | { resolved?: CommentThreadState; applicability?: CommentThreadApplicability }; + } +} diff --git a/src/commands.ts b/src/commands.ts index 50d5db81c9..9c52456e62 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -1027,7 +1027,7 @@ export function registerCommands( vscode.commands.registerCommand('pr.makeSuggestion', async (reply: CommentReply | GHPRComment | undefined) => { let potentialThread: GHPRCommentThread | undefined; if (reply === undefined) { - potentialThread = findActiveHandler()?.commentController.activeCommentThread as GHPRCommentThread | undefined; + potentialThread = findActiveHandler()?.commentController.activeCommentThread as vscode.CommentThread2 as GHPRCommentThread | undefined; } else { potentialThread = reply instanceof GHPRComment ? reply.parent : reply?.thread; } diff --git a/src/common/diffHunk.ts b/src/common/diffHunk.ts index 6fa94e3dda..b5dc608657 100644 --- a/src/common/diffHunk.ts +++ b/src/common/diffHunk.ts @@ -269,7 +269,7 @@ export async function parseDiff( const review = reviews[i]; const gitChangeType = getGitChangeType(review.status); - if (!review.patch && + if ((!review.patch && (gitChangeType !== GitChangeType.RENAME)) && // We don't need to make a SlimFileChange for empty file adds. !((gitChangeType === GitChangeType.ADD) && (review.additions === 0))) { fileChanges.push( @@ -291,7 +291,7 @@ export async function parseDiff( gitChangeType, review.filename, review.previous_filename, - review.patch, + review.patch ?? '', diffHunks, review.blob_url, ), diff --git a/src/github/interface.ts b/src/github/interface.ts index 14a7a1776e..ecc26476e2 100644 --- a/src/github/interface.ts +++ b/src/github/interface.ts @@ -204,7 +204,7 @@ export interface IRawFileChange { status: string; raw_url: string; blob_url: string; - patch: string; + patch: string | undefined; } export interface IPullRequestsPagingOptions { diff --git a/src/github/issueOverview.ts b/src/github/issueOverview.ts index 52b6a363c6..d995f536f7 100644 --- a/src/github/issueOverview.ts +++ b/src/github/issueOverview.ts @@ -16,7 +16,7 @@ import { IssueModel } from './issueModel'; import { getLabelOptions } from './quickPicks'; export class IssueOverviewPanel extends WebviewBase { - public static ID: string = 'PullRequestOverviewPanel'; + public static ID: string = 'IssueOverviewPanel'; /** * Track the currently panel. Only allow a single panel to exist at a time. */ diff --git a/src/github/prComment.ts b/src/github/prComment.ts index 9741e81f64..003a6ec8f7 100644 --- a/src/github/prComment.ts +++ b/src/github/prComment.ts @@ -46,7 +46,7 @@ export interface GHPRCommentThread extends vscode.CommentThread2 { /** * Whether the thread has been marked as resolved. */ - state: vscode.CommentThreadState; + state: { resolved: vscode.CommentThreadState; applicability: vscode.CommentThreadApplicability }; dispose: () => void; } diff --git a/src/github/utils.ts b/src/github/utils.ts index da4083efe8..6dc4d67cd7 100644 --- a/src/github/utils.ts +++ b/src/github/utils.ts @@ -105,7 +105,8 @@ export function createVSCodeCommentThreadForReviewThread( (vscodeThread as GHPRCommentThread).gitHubThreadId = thread.id; vscodeThread.comments = thread.comments.map(comment => new GHPRComment(context, comment, vscodeThread as GHPRCommentThread, githubRepositories)); - vscodeThread.state = isResolvedToResolvedState(thread.isResolved); + const resolved = isResolvedToResolvedState(thread.isResolved); + let applicability = vscode.CommentThreadApplicability.Current; if (thread.viewerCanResolve && !thread.isResolved) { vscodeThread.contextValue = 'canResolve'; @@ -114,7 +115,9 @@ export function createVSCodeCommentThreadForReviewThread( } if (thread.isOutdated) { vscodeThread.contextValue += 'outdated'; + applicability = vscode.CommentThreadApplicability.Outdated; } + vscodeThread.state = { resolved, applicability }; updateCommentThreadLabel(vscodeThread as GHPRCommentThread); vscodeThread.collapsibleState = getCommentCollapsibleState(thread, undefined, currentUser); @@ -166,8 +169,11 @@ export function updateThread(context: vscode.ExtensionContext, vscodeThread: GHP } const newResolvedState = isResolvedToResolvedState(reviewThread.isResolved); - if (vscodeThread.state !== newResolvedState) { - vscodeThread.state = newResolvedState; + if (vscodeThread.state.resolved !== newResolvedState) { + vscodeThread.state = { + resolved: newResolvedState, + applicability: vscodeThread.state.applicability + }; } vscodeThread.collapsibleState = getCommentCollapsibleState(reviewThread, expand); if (range) { @@ -189,7 +195,7 @@ export function updateThread(context: vscode.ExtensionContext, vscodeThread: GHP } export function updateCommentThreadLabel(thread: GHPRCommentThread) { - if (thread.state === vscode.CommentThreadState.Resolved) { + if (thread.state.resolved === vscode.CommentThreadState.Resolved) { thread.label = vscode.l10n.t('Marked as resolved'); return; } diff --git a/src/test/view/reviewCommentController.test.ts b/src/test/view/reviewCommentController.test.ts index dc5b86dcb9..c336935049 100644 --- a/src/test/view/reviewCommentController.test.ts +++ b/src/test/view/reviewCommentController.test.ts @@ -157,7 +157,7 @@ describe('ReviewCommentController', function () { comments: [], collapsibleState: vscode.CommentThreadCollapsibleState.Expanded, label: 'Start discussion', - state: vscode.CommentThreadState.Unresolved, + state: { resolved: vscode.CommentThreadState.Unresolved, applicability: 0 }, canReply: false, dispose: () => { }, }; diff --git a/src/view/gitHubContentProvider.ts b/src/view/gitHubContentProvider.ts index a6a7603d77..e60c1442fb 100644 --- a/src/view/gitHubContentProvider.ts +++ b/src/view/gitHubContentProvider.ts @@ -86,7 +86,7 @@ export abstract class ChangesContentProvider implements Partial { } }; } - stat(uri: any): vscode.FileStat { + stat(_uri: any): vscode.FileStat { // const params = fromGitHubURI(uri); return { diff --git a/src/view/reviewManager.ts b/src/view/reviewManager.ts index d1ee6189f3..c1e1e30b90 100644 --- a/src/view/reviewManager.ts +++ b/src/view/reviewManager.ts @@ -28,7 +28,7 @@ import { import { getReviewMode } from '../common/settingsUtils'; import { ITelemetry } from '../common/telemetry'; import { fromPRUri, fromReviewUri, KnownMediaExtensions, PRUriParams, Schemes, toReviewUri } from '../common/uri'; -import { dispose, formatError, groupBy, isPreRelease, onceEvent } from '../common/utils'; +import { dispose, formatError, groupBy, onceEvent } from '../common/utils'; import { FOCUS_REVIEW_MODE } from '../constants'; import { GitHubCreatePullRequestLinkProvider } from '../github/createPRLinkProvider'; import { FolderRepositoryManager } from '../github/folderRepositoryManager'; @@ -278,11 +278,8 @@ export class ReviewManager { } } - private hasShownLogRequest: boolean = false; private async validateStatusAndSetContext(silent: boolean, updateLayout: boolean) { - // TODO @alexr00: There's a bug where validateState never returns sometimes. It's not clear what's causing this. - // This is a temporary workaround to ensure that the validateStatueAndSetContext promise always resolves. - // Additional logs have been added, and the issue is being tracked here: https://github.com/microsoft/vscode-pull-request-git/issues/5277 + // Network errors can cause one of the GitHub API calls in validateState to never return. let timeout: NodeJS.Timeout | undefined; const timeoutPromise = new Promise(resolve => { timeout = setTimeout(() => { @@ -297,10 +294,6 @@ export class ReviewManager { } */ this._telemetry.sendTelemetryErrorEvent('pr.validateStateTimeout', { version: this._context.extension.packageJSON.version }); - if (!this.hasShownLogRequest && isPreRelease(this._context)) { - this.hasShownLogRequest = true; - vscode.window.showErrorMessage(vscode.l10n.t('A known error has occurred refreshing the repository state. Please share logs from "GitHub Pull Request" in the [tracking issue]({0}).', 'https://github.com/microsoft/vscode-pull-request-github/issues/5277')); - } } resolve(); }, 1000 * 60 * 2); diff --git a/webviews/components/merge.tsx b/webviews/components/merge.tsx index 6cc4b854e4..3531c6be53 100644 --- a/webviews/components/merge.tsx +++ b/webviews/components/merge.tsx @@ -335,7 +335,7 @@ export const PrActions = ({ pr, isSimple }: { pr: PullRequest; isSimple: boolean if (mergeable === PullRequestMergeability.Mergeable && hasWritePermission && !pr.mergeQueueEntry) { return isSimple ? : ; - } else if (hasWritePermission && !pr.mergeQueueEntry) { + } else if (!isSimple && hasWritePermission && !pr.mergeQueueEntry) { const ctx = useContext(PullRequestContext); return (