Skip to content

Display linked issue(s) from the PR Overview #5824 #6835

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
8 changes: 8 additions & 0 deletions src/github/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,14 @@ export interface PullRequest extends Issue {
viewerCanDisableAutoMerge: boolean;
isDraft?: boolean;
suggestedReviewers: SuggestedReviewerResponse[];
closingIssuesReferences?: {

This comment was marked as spam.

nodes: {
id: number,
title: string,
number: number,
state: 'CLOSED' | 'OPEN'
}[];
};
}

export enum DefaultCommitTitle {
Expand Down
1 change: 1 addition & 0 deletions src/github/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export interface PullRequest extends Issue {
mergeCommitMeta?: { title: string, description: string };
squashCommitMeta?: { title: string, description: string };
suggestedReviewers?: ISuggestedReviewer[];
closingIssues?: Pick<Issue, 'id' | 'title' | 'number' | 'state'>[]
hasComments?: boolean;
}

Expand Down
6 changes: 5 additions & 1 deletion src/github/pullRequestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
public conflicts?: string[];
public suggestedReviewers?: ISuggestedReviewer[];
public hasChangesSinceLastReview?: boolean;
public closingIssues: Pick<IssueModel, 'id' | 'title' | 'number' | 'state'>[];
private _showChangesSinceReview: boolean;
private _hasPendingReview: boolean = false;
private _onDidChangePendingReviewState: vscode.EventEmitter<boolean> = new vscode.EventEmitter<boolean>();
Expand Down Expand Up @@ -248,7 +249,10 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
super.update(item);
this.isDraft = item.isDraft;
this.suggestedReviewers = item.suggestedReviewers;

this.closingIssues = (item.closingIssues ?? []).map(issue => ({
...issue,
state: issue.state as GithubItemStateEnum
}));
if (item.isRemoteHeadDeleted != null) {
this.isRemoteHeadDeleted = item.isRemoteHeadDeleted;
}
Expand Down
3 changes: 2 additions & 1 deletion src/github/pullRequestOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
emailForCommit,
isAuthor: currentUser.login === pullRequest.author.login,
currentUserReviewState: reviewState,
revertable: pullRequest.state === GithubItemStateEnum.Merged
revertable: pullRequest.state === GithubItemStateEnum.Merged,
closingIssues: pullRequest.closingIssues
};
this._postMessage({
command: 'pr.initialize',
Expand Down
8 changes: 8 additions & 0 deletions src/github/queries.gql
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ fragment PullRequestFragment on PullRequest {
mergeCommitMessage
mergeCommitTitle
}
closingIssuesReferences(first: 50) {
nodes {
id
number
title
state
}
}
merged
mergeable
mergeQueueEntry {
Expand Down
17 changes: 17 additions & 0 deletions src/github/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { GitHubRepository, ViewerPermission } from './githubRepository';
import * as GraphQL from './graphql';
import {
AccountType,
GithubItemStateEnum,
IAccount,
IActor,
IGitHubRef,
Expand Down Expand Up @@ -780,6 +781,7 @@ export function parseGraphQLPullRequest(
commits: parseCommits(graphQLPullRequest.commits.nodes),
reactionCount: graphQLPullRequest.reactions.totalCount,
commentCount: graphQLPullRequest.comments.totalCount,
closingIssues: parseClosingIssuesReferences(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 @@ -922,6 +924,21 @@ function parseSuggestedReviewers(
return ret.sort(loginComparator);
}

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

return closingIssuesReferences.map(issue => ({
id: issue.id,
number: issue.number,
title: issue.title,
state: issue.state as GithubItemStateEnum
}));
}

/**
* Used for case insensitive sort by login
*/
Expand Down
1 change: 1 addition & 0 deletions src/github/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface PullRequest extends Issue {
lastReviewType?: ReviewType;
revertable?: boolean;
busy?: boolean;
closingIssues: Pick<Issue, 'title' | 'number' | 'state'>[];
Copy link
Member

Choose a reason for hiding this comment

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

Pick<Issue, 'title' | 'number' | 'state'> is used in more than one place, can you refactor it into an IssueReference type in this file?

}

export interface ProjectItemsReply {
Expand Down
52 changes: 48 additions & 4 deletions webviews/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
import React, { useContext } from 'react';
import { COPILOT_LOGINS } from '../../src/common/copilot';
import { gitHubLabelColor } from '../../src/common/utils';
import { IMilestone, IProjectItem, reviewerId } from '../../src/github/interface';
import { IMilestone, IProjectItem, Issue, reviewerId, } from '../../src/github/interface';
import { PullRequest } from '../../src/github/views';
import PullRequestContext from '../common/context';
import { Label } from '../common/label';
import { AuthorLink, Avatar } from '../components/user';
import { closeIcon, copilotIcon, settingsIcon } from './icon';
import { Reviewer } from './reviewer';

export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue, projectItems: projects, milestone, assignees, canAssignCopilot }: PullRequest) {
export default function Sidebar({ reviewers, labels, closingIssues, hasWritePermission, isIssue, projectItems: projects, milestone, assignees, canAssignCopilot }: PullRequest) {
const {
addReviewers,
addAssignees,
Expand Down Expand Up @@ -54,7 +54,7 @@ export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue
</div>
{reviewers && reviewers.length ? (
reviewers.map(state => (
<Reviewer key={reviewerId(state.reviewer)} {...{reviewState: state}} />
<Reviewer key={reviewerId(state.reviewer)} {...{ reviewState: state }} />
))
) : (
<div className="section-placeholder">None yet</div>
Expand Down Expand Up @@ -199,7 +199,27 @@ export default function Sidebar({ reviewers, labels, hasWritePermission, isIssue
{milestone ? (
<Milestone key={milestone.title} {...milestone} canDelete={hasWritePermission} />
) : (
<div className="section-placeholder">No milestone</div>
<>
<div className="section-placeholder">No milestone</div>
</>
)}
</div>
<div id="closingIssues" className="section">
<div className="section-header">
<div className="section-title">Linked Issues</div>
</div>
{closingIssues.length > 0 ? (
<div className="p-2">
Copy link
Member

Choose a reason for hiding this comment

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

Please use a class name that is consistent with the class naming scheme in the file.

Copy link
Member

Choose a reason for hiding this comment

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

Same for gap-2 below.

{closingIssues.map(issue => (
<div className="section-item reviewer">
<div className="avatar-with-author gap-2">
<IssueItem key={issue.title} issue={issue} />
</div>
</div>
))}
</div>
) : (
<div className="p-4 text-sm text-gray-500 text-center">None yet</div>
)}
</div>
</div>
Expand Down Expand Up @@ -273,3 +293,27 @@ function Project(project: IProjectItem & { canDelete: boolean }) {
</div>
);
}

function IssueItem({ issue }: { issue: Pick<Issue, 'title' | 'number' | 'state'> }) {
return (
<>
<IssueStateIcon state={issue.state} />
<span className="h2">{issue.title}</span>
</>

);
}

function IssueStateIcon({ state }: { state: string }) {
const normalizedState = state.toLowerCase().trim();

switch (normalizedState) {
case 'open':
return settingsIcon;
Copy link
Member

Choose a reason for hiding this comment

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

Why not issueIcon?

case 'closed':
return closeIcon;
default:
return closeIcon;
}
}

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