Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Linking PR to issue(s) from the PR Overview #5825

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"enabledApiProposals": [
"activeComment",
"commentingRangeHint",
"commentThreadApplicability",
"contribCommentsViewThreadMenus",
"tokenInformation",
"contribShareMenu",
Expand All @@ -29,7 +30,7 @@
"tabInputTextMerge",
"treeViewMarkdownMessage"
],
"version": "0.82.0",
"version": "0.84.0",
"publisher": "GitHub",
"engines": {
"vscode": "^1.88.0"
Expand Down Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions src/@types/vscode.proposed.commentThreadApplicability.d.ts
Original file line number Diff line number Diff line change
@@ -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 };
}
}
2 changes: 1 addition & 1 deletion src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
4 changes: 2 additions & 2 deletions src/common/diffHunk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -291,7 +291,7 @@ export async function parseDiff(
gitChangeType,
review.filename,
review.previous_filename,
review.patch,
review.patch ?? '',
diffHunks,
review.blob_url,
),
Expand Down
9 changes: 9 additions & 0 deletions src/github/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
3 changes: 2 additions & 1 deletion src/github/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ export interface PullRequest extends Issue {
mergeCommitMeta?: { title: string, description: string };
squashCommitMeta?: { title: string, description: string };
suggestedReviewers?: ISuggestedReviewer[];
closingIssuesReferences?: Pick<Issue, 'id' | 'number' | 'state' | 'title'>[]
}

export interface IRawFileChange {
Expand All @@ -203,7 +204,7 @@ export interface IRawFileChange {
status: string;
raw_url: string;
blob_url: string;
patch: string;
patch: string | undefined;
}

export interface IPullRequestsPagingOptions {
Expand Down
2 changes: 1 addition & 1 deletion src/github/issueOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { IssueModel } from './issueModel';
import { getLabelOptions } from './quickPicks';

export class IssueOverviewPanel<TItem extends IssueModel = IssueModel> 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.
*/
Expand Down
2 changes: 1 addition & 1 deletion src/github/prComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
5 changes: 4 additions & 1 deletion src/github/pullRequestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import {
GithubItemStateEnum,
IAccount,
IRawFileChange,
Issue,
ISuggestedReviewer,
ITeam,
MergeMethod,
Expand Down Expand Up @@ -115,6 +116,8 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
public mergeQueueEntry?: MergeQueueEntry;
public suggestedReviewers?: ISuggestedReviewer[];
public hasChangesSinceLastReview?: boolean;
public issues: Partial<Issue>[];

private _showChangesSinceReview: boolean;
private _hasPendingReview: boolean = false;
private _onDidChangePendingReviewState: vscode.EventEmitter<boolean> = new vscode.EventEmitter<boolean>();
Expand All @@ -137,7 +140,6 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
private _comments: readonly IComment[] | undefined;
private _onDidChangeComments: vscode.EventEmitter<void> = new vscode.EventEmitter();
public readonly onDidChangeComments: vscode.Event<void> = this._onDidChangeComments.event;

// Whether the pull request is currently checked out locally
private _isActive: boolean;
public get isActive(): boolean {
Expand Down Expand Up @@ -245,6 +247,7 @@ export class PullRequestModel extends IssueModel<PullRequest> 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;
Expand Down
1 change: 1 addition & 0 deletions src/github/pullRequestOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
projectItems: pullRequest.item.projectItems,
milestone: pullRequest.milestone,
assignees: pullRequest.assignees,
issues: pullRequest.issues,
continueOnGitHub,
emailForCommit: currentUser.email,
isAuthor: currentUser.login === pullRequest.author.login,
Expand Down
9 changes: 9 additions & 0 deletions src/github/queriesExtra.gql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ fragment PullRequestFragment on PullRequest {
id
}
}
closingIssuesReferences(first: 50) {
nodes {
id
databaseId
body
number
title
}
}
commits(first: 50) {
nodes {
commit {
Expand Down
44 changes: 40 additions & 4 deletions src/github/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -714,6 +720,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);
Expand Down Expand Up @@ -797,6 +804,35 @@ function parseComments(comments: GraphQL.AbbreviatedIssueComment[] | undefined,
return parsedComments;
}

function parseIssues(issues: Pick<GraphQL.PullRequest, 'id' | 'databaseId' | 'number' | 'title' | 'state'>[] | 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,
Expand Down
2 changes: 2 additions & 0 deletions src/github/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
ILabel,
IMilestone,
IProjectItem,
Issue,
MergeMethod,
MergeMethodsAvailability,
MergeQueueState,
Expand Down Expand Up @@ -47,6 +48,7 @@ export interface PullRequest {
commitsCount: number;
projectItems: IProjectItem[] | undefined;
milestone: IMilestone | undefined;
issues: Partial<Issue>[];
repositoryDefaultBranch: string;
/**
* User can edit PR title and description (author or user with push access)
Expand Down
2 changes: 1 addition & 1 deletion src/test/view/reviewCommentController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: () => { },
};
Expand Down
2 changes: 1 addition & 1 deletion src/view/gitHubContentProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export abstract class ChangesContentProvider implements Partial<vscode.FileSyste
return { dispose: () => { } };
}

stat(uri: any): vscode.FileStat {
stat(_uri: any): vscode.FileStat {
// const params = fromGitHubURI(uri);

return {
Expand Down
11 changes: 2 additions & 9 deletions src/view/reviewManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<void>(resolve => {
timeout = setTimeout(() => {
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion webviews/components/merge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ export const PrActions = ({ pr, isSimple }: { pr: PullRequest; isSimple: boolean

if (mergeable === PullRequestMergeability.Mergeable && hasWritePermission && !pr.mergeQueueEntry) {
return isSimple ? <MergeSimple {...pr} /> : <Merge {...pr} />;
} else if (hasWritePermission && !pr.mergeQueueEntry) {
} else if (!isSimple && hasWritePermission && !pr.mergeQueueEntry) {
const ctx = useContext(PullRequestContext);
return (
<AutoMerge
Expand Down
37 changes: 36 additions & 1 deletion webviews/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -177,6 +177,30 @@ export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue
<div className="section-placeholder">No milestone</div>
)}
</div>
<div id="linked-issues" className="section">
<div className="section-header" onClick={async () => {
const newMilestone = await addMilestone();
updatePR({ milestone: newMilestone.added });
}}>
<div className="section-title">Linked issues</div>
{hasWritePermission ? (
<button
className="icon-button"
title="Link Issue">
{settingsIcon}
</button>
) : null}
</div>
{issues.length ? (
<div className="issues-list">
{issues.map(issue => (
<Issue key={issue.title} {...issue} />
))}
</div>
) : (
<div className="section-placeholder">None yet</div>
)}
</div>
</div>
);
}
Expand Down Expand Up @@ -248,3 +272,14 @@ function Project(project: IProjectItem & { canDelete: boolean }) {
</div>
);
}

function Issue(issue: any) {
return (
<div className="issues-list">
<div className="issue-item">
<h1>{issue.title}</h1>
{/* Add more details about the issue as needed */}
</div>
</div>
);
}
Loading