Skip to content
Merged
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
9 changes: 9 additions & 0 deletions src/github/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,15 @@ export interface PullRequest extends Issue {
suggestedReviewers: SuggestedReviewerResponse[];
additions?: number;
deletions?: number;
closingIssuesReferences?: {
Comment thread
alexr00 marked this conversation as resolved.
nodes: {
id: number,
title: string,
number: number,
state: 'CLOSED' | 'OPEN',
url: string,
}[];
Comment thread
alexr00 marked this conversation as resolved.
};
}

export enum DefaultCommitTitle {
Expand Down
9 changes: 9 additions & 0 deletions src/github/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,14 @@ export interface Issue {
reactions: Reaction[];
}

export interface IssueReference {
id: number;
number: number;
title: string;
state: GithubItemStateEnum;
url: string;
}

export interface PullRequest extends Issue {
isDraft?: boolean;
isRemoteHeadDeleted?: boolean;
Expand All @@ -242,6 +250,7 @@ export interface PullRequest extends Issue {
mergeCommitMeta?: { title: string, description: string };
squashCommitMeta?: { title: string, description: string };
suggestedReviewers?: ISuggestedReviewer[];
closingIssues?: IssueReference[]
Comment thread
alexr00 marked this conversation as resolved.
hasComments?: boolean;
additions?: number;
deletions?: number;
Expand Down
4 changes: 3 additions & 1 deletion src/github/pullRequestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
IGitTreeItem,
IRawFileChange,
IRawFileContent,
IssueReference,
ISuggestedReviewer,
ITeam,
MergeMethod,
Expand Down Expand Up @@ -137,6 +138,7 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
public conflicts?: string[];
public suggestedReviewers?: ISuggestedReviewer[];
public hasChangesSinceLastReview?: boolean;
public closingIssues: IssueReference[] = [];
private _showChangesSinceReview: boolean;
private _hasPendingReview: boolean = false;
private _onDidChangePendingReviewState: vscode.EventEmitter<boolean> = this._register(new vscode.EventEmitter<boolean>());
Expand Down Expand Up @@ -265,7 +267,7 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
}

this.suggestedReviewers = item.suggestedReviewers;

this.closingIssues = item.closingIssues ?? [];
if (item.isRemoteHeadDeleted != null) {
this.isRemoteHeadDeleted = item.isRemoteHeadDeleted;
}
Expand Down
12 changes: 10 additions & 2 deletions src/github/pullRequestOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { IssueOverviewPanel, panelKey } from './issueOverview';
import { isCopilotOnMyBehalf, PullRequestModel } from './pullRequestModel';
import { PullRequestReviewCommon, ReviewContext } from './pullRequestReviewCommon';
import { branchPicks, pickEmail, reviewersQuickPick } from './quickPicks';
import { parseReviewers, processDiffLinks, processPermalinks } from './utils';
import { ISSUE_OR_URL_EXPRESSION, parseIssueExpressionOutput, parseReviewers, processDiffLinks, processPermalinks } from './utils';
import { CancelCodingAgentReply, ChangeBaseReply, ChangeReviewersReply, DeleteReviewResult, MergeArguments, MergeResult, PullRequest, ReadyForReviewAndMergeContext, ReadyForReviewContext, ReviewCommentContext, ReviewType, UnresolvedIdentity } from './views';
import { debounce } from '../common/async';
import { COPILOT_ACCOUNTS, IComment } from '../common/comment';
Expand All @@ -38,6 +38,7 @@ import Logger from '../common/logger';
import { CHECKOUT_DEFAULT_BRANCH, CHECKOUT_PULL_REQUEST_BASE_BRANCH, DEFAULT_MERGE_METHOD, DELETE_BRANCH_AFTER_MERGE, POST_DONE, PR_SETTINGS_NAMESPACE } from '../common/settingKeys';
import { ITelemetry } from '../common/telemetry';
import { EventType, ReviewEvent, SessionLinkInfo, TimelineEvent } from '../common/timelineEvent';
import { toOpenIssueWebviewUri } from '../common/uri';
import { asPromise, formatError } from '../common/utils';
import { IRequestMessage, PULL_REQUEST_OVERVIEW_VIEW_TYPE } from '../common/webview';
import { toCheckRunLogUri } from '../view/checkRunLogContentProvider';
Expand Down Expand Up @@ -458,7 +459,14 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
currentUserReviewState: reviewState,
revertable: pullRequest.state === GithubItemStateEnum.Merged,
isCopilotOnMyBehalf: await isCopilotOnMyBehalf(pullRequest, currentUser, coAuthors),
generateDescriptionTitle: this.getGenerateDescriptionTitle()
generateDescriptionTitle: this.getGenerateDescriptionTitle(),
closingIssues: await Promise.all((pullRequest.closingIssues ?? []).map(async issue => {
const parsed = parseIssueExpressionOutput(issue.url.match(ISSUE_OR_URL_EXPRESSION));
const owner = parsed?.owner ?? pullRequest.remote.owner;
const repo = parsed?.name ?? pullRequest.remote.repositoryName;
Comment thread
alexr00 marked this conversation as resolved.
const webviewUri = await toOpenIssueWebviewUri({ owner, repo, issueNumber: issue.number });
return { ...issue, url: webviewUri.toString() };
})),
};
this._postMessage({
command: 'pr.initialize',
Expand Down
9 changes: 9 additions & 0 deletions src/github/queries.gql
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,15 @@ fragment PullRequestFragment on PullRequest {
mergeCommitMessage
mergeCommitTitle
}
closingIssuesReferences(first: 50) {
Comment thread
alexr00 marked this conversation as resolved.
nodes {
id
number
title
state
url
}
Comment thread
alexr00 marked this conversation as resolved.
}
merged
mergeable
mergeQueueEntry {
Expand Down
9 changes: 9 additions & 0 deletions src/github/queriesExtra.gql
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ fragment PullRequestFragment on PullRequest {
mergeCommitMessage
mergeCommitTitle
}
closingIssuesReferences(first: 50) {
nodes {
id
number
title
state
url
}
Comment thread
alexr00 marked this conversation as resolved.
}
merged
mergeable
mergeQueueEntry {
Expand Down
9 changes: 9 additions & 0 deletions src/github/queriesLimited.gql
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,15 @@ fragment PullRequestFragment on PullRequest {
}
url
}
closingIssuesReferences(first: 50) {
nodes {
id
number
title
state
url
}
Comment thread
alexr00 marked this conversation as resolved.
}
merged
mergeable
mergeStateStatus
Expand Down
17 changes: 17 additions & 0 deletions src/github/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,7 @@ export async function parseGraphQLPullRequest(
commentCount: graphQLPullRequest.comments.totalCount,
additions: graphQLPullRequest.additions,
deletions: graphQLPullRequest.deletions,
closingIssues: parseClosingIssuesReferences(graphQLPullRequest.closingIssuesReferences?.nodes),
};
Comment on lines 924 to 928
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New closingIssues parsing/mapping is untested. Since parseGraphQLPullRequest already has unit coverage under src/test (e.g. prsTree.test.ts), consider adding a case that includes closingIssuesReferences to verify state mapping and that the parsed references are propagated correctly.

Copilot generated this review using guidance from repository custom instructions.
pr.mergeCommitMeta = parseCommitMeta(graphQLPullRequest.baseRepository.mergeCommitTitle, graphQLPullRequest.baseRepository.mergeCommitMessage, pr);
pr.squashCommitMeta = parseCommitMeta(graphQLPullRequest.baseRepository.squashMergeCommitTitle, graphQLPullRequest.baseRepository.squashMergeCommitMessage, pr);
Expand Down Expand Up @@ -1068,6 +1069,22 @@ function parseSuggestedReviewers(
return ret.sort(loginComparator);
}

function parseClosingIssuesReferences(
closingIssuesReferences: Array<{ id: number, number: number, title: string, state: string, url: string }> | undefined
): Array<{ id: number, number: number, title: string, state: GithubItemStateEnum, url: string }> {
if (!closingIssuesReferences) {
return [];
}

return closingIssuesReferences.map(issue => ({
id: issue.id,
number: issue.number,
title: issue.title,
state: issue.state === 'OPEN' ? GithubItemStateEnum.Open : GithubItemStateEnum.Closed,
url: issue.url,
}));
Comment thread
alexr00 marked this conversation as resolved.
}

/**
* Used for case insensitive sort by login
*/
Expand Down
8 changes: 8 additions & 0 deletions src/github/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ export enum ReviewType {
RequestChanges = 'requestChanges',
}

export interface IssueReference {
number: number;
title: string;
state: GithubItemStateEnum;
url: string;
}
Comment thread
alexr00 marked this conversation as resolved.

export interface DisplayLabel extends ILabel {
displayName: string;
}
Expand Down Expand Up @@ -112,6 +119,7 @@ export interface PullRequest extends Issue {
busy?: boolean;
loadingCommit?: string;
generateDescriptionTitle?: string;
closingIssues: IssueReference[];
}

export interface ProjectItemsReply {
Expand Down
29 changes: 29 additions & 0 deletions webviews/common/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,35 @@ body img.avatar {
fill: var(--vscode-issues-open);
}

.section-icon.issue-open svg path {
fill: var(--vscode-issues-open);
}

.section-icon.issue-closed svg path {
fill: var(--vscode-issues-closed);
}

.issue-item {
display: flex;
align-items: center;
gap: 6px;
color: var(--vscode-foreground);
text-decoration: none;
overflow: hidden;
}

.issue-item:hover {
text-decoration: underline;
color: var(--vscode-foreground);
}

.issue-item-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
min-width: 0;
}
.reviewer-icons {
display: flex;
gap: 4px;
Expand Down
33 changes: 29 additions & 4 deletions webviews/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
*--------------------------------------------------------------------------------------------*/

import React, { useContext, useEffect, useRef, useState } from 'react';
import { closeIcon, copilotIcon, settingsIcon } from './icon';
import { closeIcon, copilotIcon, issuescon, passIcon, settingsIcon } from './icon';
import { Reviewer } from './reviewer';
import { COPILOT_LOGINS } from '../../src/common/copilot';
import { gitHubLabelColor } from '../../src/common/utils';
import { IAccount, IMilestone, IProjectItem, isITeam, reviewerId, reviewerLabel, ReviewState } from '../../src/github/interface';
import { ChangeReviewersReply, PullRequest } from '../../src/github/views';
import { GithubItemStateEnum, IAccount, IMilestone, IProjectItem, isITeam, reviewerId, reviewerLabel, ReviewState } from '../../src/github/interface';
import { ChangeReviewersReply, IssueReference, PullRequest } from '../../src/github/views';
import PullRequestContext from '../common/context';
import { Label } from '../common/label';
import { AuthorLink, Avatar } from '../components/user';
Expand Down Expand Up @@ -53,7 +53,7 @@ function Section({
);
}

export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue, projectItems: projects, milestone, assignees, canAssignCopilot, canRequestCopilotReview }: PullRequest) {
export default function Sidebar({ reviewers, labels, closingIssues, hasWritePermission, isIssue, projectItems: projects, milestone, assignees, canAssignCopilot, canRequestCopilotReview }: PullRequest) {
Comment thread
alexr00 marked this conversation as resolved.
const {
addReviewers,
addReviewerCopilot,
Expand Down Expand Up @@ -268,6 +268,18 @@ export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue
<div className="section-placeholder">No milestone</div>
)}
</Section>

{closingIssues.length > 0 && (
<Section
id="closingIssues"
title="Linked Issues"
hasWritePermission={false}
>
{closingIssues.map(issue => (
<IssueItem key={issue.number} issue={issue} />
))}
</Section>
)}
</div>
);
}
Expand Down Expand Up @@ -577,3 +589,16 @@ function ConvertToDraft() {
</div>
);
}

function IssueItem({ issue }: { issue: IssueReference }) {
const isOpen = issue.state === GithubItemStateEnum.Open;
return (
<a className="issue-item" href={issue.url} title={`#${issue.number} ${issue.title}`}>
<span className={`section-icon ${isOpen ? 'issue-open' : 'issue-closed'}`}>
{isOpen ? issuescon : passIcon}
</span>
<span className="issue-item-text">#{issue.number} {issue.title}</span>
</a>
);
}

1 change: 1 addition & 0 deletions webviews/editorWebview/test/builder/pullRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export const PullRequestBuilder = createBuilderClass<PullRequest>()({
hasReviewDraft: { default: false },
busy: { default: undefined },
lastReviewType: { default: undefined },
closingIssues: { default: [] },
canAssignCopilot: { default: false },
canRequestCopilotReview: { default: false },
isCopilotOnMyBehalf: { default: false },
Expand Down