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 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
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
8 changes: 8 additions & 0 deletions src/github/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ export interface Issue {
reactionCount: number;
}

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

export interface PullRequest extends Issue {
isDraft?: boolean;
isRemoteHeadDeleted?: boolean;
Expand All @@ -223,6 +230,7 @@ export interface PullRequest extends Issue {
mergeCommitMeta?: { title: string, description: string };
squashCommitMeta?: { title: string, description: string };
suggestedReviewers?: ISuggestedReviewer[];
closingIssues?: IssueReference[]
hasComments?: boolean;
}

Expand Down
8 changes: 2 additions & 6 deletions src/github/issueModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
UpdateIssueResponse,
} from './graphql';
import { GithubItemStateEnum, IAccount, IIssueEditData, IMilestone, IProject, IProjectItem, Issue } from './interface';
import { parseGraphQlIssueComment, parseGraphQLTimelineEvents } from './utils';
import { parseGraphQlIssueComment, parseGraphQLTimelineEvents, parsePullRequestState } from './utils';

export class IssueModel<TItem extends Issue = Issue> {
static ID = 'IssueModel';
Expand Down Expand Up @@ -104,11 +104,7 @@ export class IssueModel<TItem extends Issue = Issue> {
}

protected updateState(state: string) {
if (state.toLowerCase() === 'open') {
this.state = GithubItemStateEnum.Open;
} else {
this.state = GithubItemStateEnum.Closed;
}
this.state = parsePullRequestState(state);
}

update(issue: TItem): void {
Expand Down
14 changes: 6 additions & 8 deletions src/github/pullRequestModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
IGitTreeItem,
IRawFileChange,
IRawFileContent,
IssueReference,
ISuggestedReviewer,
ITeam,
MergeMethod,
Expand All @@ -83,6 +84,7 @@ import {
parseGraphQLTimelineEvents,
parseMergeability,
parseMergeQueueEntry,
parsePullRequestState,
restPaginate,
} from './utils';

Expand Down Expand Up @@ -121,6 +123,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> = new vscode.EventEmitter<boolean>();
Expand Down Expand Up @@ -235,20 +238,15 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
public base: GitHubRef;

protected override updateState(state: string) {
if (state.toLowerCase() === 'open') {
this.state = GithubItemStateEnum.Open;
} else if (state.toLowerCase() === 'merged' || this.item.merged) {
this.state = GithubItemStateEnum.Merged;
} else {
this.state = GithubItemStateEnum.Closed;
}
const newState = parsePullRequestState(state);
this.state = this.item.merged ? GithubItemStateEnum.Merged : newState;
}

override update(item: PullRequest): void {
super.update(item);
this.isDraft = item.isDraft;
this.suggestedReviewers = item.suggestedReviewers;

this.closingIssues = item.closingIssues ?? [];
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
27 changes: 27 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 @@ -741,6 +742,16 @@ export function parseMergeability(mergeability: 'UNKNOWN' | 'MERGEABLE' | 'CONFL
return parsed;
}

export function parsePullRequestState(state: string): GithubItemStateEnum {
if (state.toLowerCase() === 'open') {
return GithubItemStateEnum.Open;
} else if (state.toLowerCase() === 'merged') {
return GithubItemStateEnum.Merged;
} else {
return GithubItemStateEnum.Closed;
}
}

export function parseGraphQLPullRequest(
graphQLPullRequest: GraphQL.PullRequest,
githubRepository: GitHubRepository,
Expand Down Expand Up @@ -780,6 +791,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 +934,21 @@ function parseSuggestedReviewers(
return ret.sort(loginComparator);
}

function parseClosingIssuesReferences(
closingIssuesReferences: Array<{ id: number, number: number, title: string, state: string }> | 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: parsePullRequestState(issue.state)
}));
}

/**
* 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 },
});