Skip to content

Commit 2679e6f

Browse files
Adds issue icons to branches in graph, and setting to toggle (#3889)
1 parent 69b6395 commit 2679e6f

File tree

8 files changed

+160
-3
lines changed

8 files changed

+160
-3
lines changed

docs/telemetry-events.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,7 @@ or
849849
'context.config.defaultItemLimit': number,
850850
'context.config.dimMergeCommits': boolean,
851851
'context.config.highlightRowsOnRefHover': boolean,
852+
'context.config.issues.enabled': boolean,
852853
'context.config.layout': 'editor' | 'panel',
853854
'context.config.minimap.additionalTypes': string,
854855
'context.config.minimap.dataType': 'commits' | 'lines',

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1157,12 +1157,19 @@
11571157
"scope": "window",
11581158
"order": 702
11591159
},
1160+
"gitlens.graph.issues.enabled": {
1161+
"type": "boolean",
1162+
"default": true,
1163+
"markdownDescription": "Specifies whether to show associated issues on branches in the _Commit Graph_. Requires a connection to a supported issue service (e.g. GitHub)",
1164+
"scope": "window",
1165+
"order": 703
1166+
},
11601167
"gitlens.graph.pullRequests.enabled": {
11611168
"type": "boolean",
11621169
"default": true,
11631170
"markdownDescription": "Specifies whether to show associated pull requests on remote branches in the _Commit Graph_. Requires a connection to a supported remote service (e.g. GitHub)",
11641171
"scope": "window",
1165-
"order": 703
1172+
"order": 704
11661173
},
11671174
"gitlens.graph.avatars": {
11681175
"type": "boolean",

src/config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ export interface GraphConfig {
347347
readonly defaultItemLimit: number;
348348
readonly dimMergeCommits: boolean;
349349
readonly highlightRowsOnRefHover: boolean;
350+
readonly issues: {
351+
readonly enabled: boolean;
352+
};
350353
readonly layout: 'editor' | 'panel';
351354
readonly minimap: {
352355
readonly enabled: boolean;

src/plus/webviews/graph/graphWebview.ts

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
} from '../../../config';
2020
import { GlyphChars } from '../../../constants';
2121
import { GlCommand } from '../../../constants.commands';
22+
import { HostingIntegrationId, IssueIntegrationId } from '../../../constants.integrations';
2223
import type { StoredGraphFilters, StoredGraphRefType } from '../../../constants.storage';
2324
import type { GraphShownTelemetryContext, GraphTelemetryContext, TelemetryEvents } from '../../../constants.telemetry';
2425
import type { Container } from '../../../container';
@@ -50,6 +51,7 @@ import { GitSearchError } from '../../../git/errors';
5051
import { CommitFormatter } from '../../../git/formatters/commitFormatter';
5152
import type { GitBranch } from '../../../git/models/branch';
5253
import {
54+
getAssociatedIssuesForBranch,
5355
getBranchId,
5456
getBranchNameWithoutRemote,
5557
getDefaultBranchName,
@@ -62,6 +64,7 @@ import { isStash } from '../../../git/models/commit';
6264
import { splitCommitMessage } from '../../../git/models/commit.utils';
6365
import { GitContributor } from '../../../git/models/contributor';
6466
import type { GitGraph, GitGraphRowType } from '../../../git/models/graph';
67+
import type { IssueShape } from '../../../git/models/issue';
6568
import type { PullRequest } from '../../../git/models/pullRequest';
6669
import {
6770
getComparisonRefsForPullRequest,
@@ -113,7 +116,7 @@ import {
113116
import { configuration } from '../../../system/vscode/configuration';
114117
import { getContext, onDidChangeContext } from '../../../system/vscode/context';
115118
import type { OpenWorkspaceLocation } from '../../../system/vscode/utils';
116-
import { isDarkTheme, isLightTheme, openWorkspace } from '../../../system/vscode/utils';
119+
import { isDarkTheme, isLightTheme, openUrl, openWorkspace } from '../../../system/vscode/utils';
117120
import { isWebviewItemContext, isWebviewItemGroupContext, serializeWebviewItemContext } from '../../../system/webview';
118121
import { DeepLinkActionType } from '../../../uris/deepLinks/deepLink';
119122
import { RepositoryFolderNode } from '../../../views/nodes/abstract/repositoryFolderNode';
@@ -150,6 +153,8 @@ import type {
150153
GraphHostingServiceType,
151154
GraphIncludeOnlyRef,
152155
GraphIncludeOnlyRefs,
156+
GraphIssueContextValue,
157+
GraphIssueTrackerType,
153158
GraphItemContext,
154159
GraphItemGroupContext,
155160
GraphItemRefContext,
@@ -1019,6 +1024,8 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
10191024
}
10201025
} else if (e.metadata.type === 'pullRequest' && isGraphItemTypedContext(item, 'pullrequest')) {
10211026
return void this.openPullRequestOnRemote(item);
1027+
} else if (e.metadata.type === 'issue' && isGraphItemTypedContext(item, 'issue')) {
1028+
return void this.openIssueOnRemote(item);
10221029
}
10231030

10241031
return;
@@ -1361,6 +1368,70 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
13611368
metadata.upstream = upstreamMetadata;
13621369

13631370
this._refsMetadata.set(id, metadata);
1371+
continue;
1372+
}
1373+
1374+
// TODO: Issue metadata needs to update for a branch whenever we add an associated issue for it, so that we don't
1375+
// have to completely refresh the component to see the new issue
1376+
if (type === 'issue') {
1377+
let issues: IssueShape[] | undefined = await getAssociatedIssuesForBranch(
1378+
this.container,
1379+
branch,
1380+
).then(issues => issues.value);
1381+
if (issues == null || issues.length === 0) {
1382+
issues = await branch.getEnrichedAutolinks().then(async enrichedAutolinks => {
1383+
if (enrichedAutolinks == null) return undefined;
1384+
return (
1385+
await Promise.all(
1386+
[...enrichedAutolinks.values()].map(async ([issueOrPullRequestPromise]) =>
1387+
// eslint-disable-next-line no-return-await
1388+
issueOrPullRequestPromise != null ? await issueOrPullRequestPromise : undefined,
1389+
),
1390+
)
1391+
).filter<IssueShape>(
1392+
(a?: unknown): a is IssueShape =>
1393+
a != null && a instanceof Object && 'type' in a && a.type === 'issue',
1394+
);
1395+
});
1396+
1397+
if (issues == null || issues.length === 0) {
1398+
metadata.issue = null;
1399+
this._refsMetadata.set(id, metadata);
1400+
continue;
1401+
}
1402+
}
1403+
1404+
const issuesMetadata = [];
1405+
for (const issue of issues) {
1406+
const issueTracker = toGraphIssueTrackerType(issue.provider.id);
1407+
if (issueTracker == null) continue;
1408+
issuesMetadata.push({
1409+
displayId: issue.id,
1410+
id: issue.nodeId ?? issue.id,
1411+
// TODO: This is a hack/workaround because the graph component doesn't support this in the tooltip.
1412+
// Update this once that is fixed.
1413+
title: `${issue.title}\nDouble-click to open issue on ${issue.provider.name}`,
1414+
issueTrackerType: issueTracker,
1415+
url: issue.url,
1416+
context: serializeWebviewItemContext<GraphItemContext>({
1417+
webviewItem: `gitlens:issue`,
1418+
webviewItemValue: {
1419+
type: 'issue',
1420+
id: issue.id,
1421+
url: issue.url,
1422+
provider: {
1423+
id: issue.provider.id,
1424+
name: issue.provider.name,
1425+
domain: issue.provider.domain,
1426+
icon: issue.provider.icon,
1427+
},
1428+
},
1429+
}),
1430+
});
1431+
}
1432+
1433+
metadata.issue = issuesMetadata;
1434+
this._refsMetadata.set(id, metadata);
13641435
}
13651436
}
13661437
}
@@ -2327,6 +2398,10 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
23272398
private getEnabledRefMetadataTypes(): GraphRefMetadataType[] {
23282399
const types: GraphRefMetadataType[] = [];
23292400

2401+
if (configuration.get('graph.issues.enabled')) {
2402+
types.push('issue');
2403+
}
2404+
23302405
if (configuration.get('graph.pullRequests.enabled')) {
23312406
types.push('pullRequest');
23322407
}
@@ -3559,6 +3634,17 @@ export class GraphWebviewProvider implements WebviewProvider<State, State, Graph
35593634
return Promise.resolve();
35603635
}
35613636

3637+
@log()
3638+
private openIssueOnRemote(item?: GraphItemContext) {
3639+
if (isGraphItemTypedContext(item, 'issue')) {
3640+
const { url } = item.webviewItemValue;
3641+
// TODO: Add a command for this. See openPullRequestOnRemote above.
3642+
void openUrl(url);
3643+
}
3644+
3645+
return Promise.resolve();
3646+
}
3647+
35623648
@log()
35633649
private async compareAncestryWithWorking(item?: GraphItemContext) {
35643650
const ref = this.getGraphItemRef(item);
@@ -4016,6 +4102,7 @@ function isGraphItemTypedContext(
40164102
item: unknown,
40174103
type: 'upstreamStatus',
40184104
): item is GraphItemTypedContext<GraphUpstreamStatusContextValue>;
4105+
function isGraphItemTypedContext(item: unknown, type: 'issue'): item is GraphItemTypedContext<GraphIssueContextValue>;
40194106
function isGraphItemTypedContext(
40204107
item: unknown,
40214108
type: GraphItemTypedContextValue['type'],
@@ -4060,3 +4147,16 @@ export function hasGitReference(o: unknown): o is { ref: GitReference } {
40604147

40614148
return isGitReference(o.ref);
40624149
}
4150+
4151+
function toGraphIssueTrackerType(id: string): GraphIssueTrackerType | undefined {
4152+
switch (id) {
4153+
case HostingIntegrationId.GitHub:
4154+
return 'github';
4155+
case HostingIntegrationId.GitLab:
4156+
return 'gitlab';
4157+
case IssueIntegrationId.Jira:
4158+
return 'jiraCloud';
4159+
default:
4160+
return undefined;
4161+
}
4162+
}

src/plus/webviews/graph/protocol.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
Head,
1212
HostingServiceType,
1313
IncludeOnlyRefsById,
14+
IssueTrackerType,
1415
PullRequestMetadata,
1516
RefMetadata,
1617
RefMetadataItem,
@@ -546,7 +547,8 @@ export type GraphItemTypedContext<T = GraphItemTypedContextValue> = WebviewItemC
546547
export type GraphItemTypedContextValue =
547548
| GraphContributorContextValue
548549
| GraphPullRequestContextValue
549-
| GraphUpstreamStatusContextValue;
550+
| GraphUpstreamStatusContextValue
551+
| GraphIssueContextValue;
550552

551553
export type GraphColumnsContextValue = string;
552554

@@ -567,6 +569,13 @@ export interface GraphPullRequestContextValue {
567569
provider: ProviderReference;
568570
}
569571

572+
export interface GraphIssueContextValue {
573+
type: 'issue';
574+
id: string;
575+
url: string;
576+
provider: ProviderReference;
577+
}
578+
570579
export interface GraphBranchContextValue {
571580
type: 'branch';
572581
ref: GitBranchReference;
@@ -593,3 +602,5 @@ export interface GraphUpstreamStatusContextValue {
593602
ahead: number;
594603
behind: number;
595604
}
605+
606+
export type GraphIssueTrackerType = IssueTrackerType;

src/webviews/apps/plus/graph/GraphWrapper.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ const createIconElements = (): Record<string, ReactElement> => {
166166
'changes',
167167
'files',
168168
'worktree',
169+
'issue-github',
170+
'issue-gitlab',
171+
'issue-jiraCloud',
169172
];
170173

171174
const miniIconList = ['upstream-ahead', 'upstream-behind'];

src/webviews/apps/plus/graph/graph.scss

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,27 @@ button:not([disabled]),
823823
@include iconUtils.glicon('worktrees-view');
824824
}
825825
}
826+
827+
&--issue-github {
828+
&::before {
829+
font-family: codicon;
830+
@include iconUtils.codicon('github-inverted');
831+
}
832+
}
833+
834+
&--issue-gitlab {
835+
&::before {
836+
font-family: 'glicons';
837+
@include iconUtils.glicon('provider-gitlab');
838+
}
839+
}
840+
841+
&--issue-jiraCloud {
842+
&::before {
843+
font-family: 'glicons';
844+
@include iconUtils.glicon('provider-jira');
845+
}
846+
}
826847
}
827848

828849
.titlebar {

src/webviews/apps/settings/partials/commit-graph.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,17 @@ <h2>
194194
</div>
195195
</div>
196196

197+
<div class="setting">
198+
<div class="setting__input">
199+
<input id="graph.issues.enabled" name="graph.issues.enabled" type="checkbox" data-setting />
200+
<label for="graph.issues.enabled">Show associated issues on branches</label>
201+
</div>
202+
<p class="setting__hint hidden" data-visibility="graph.issues.enabled">
203+
<i class="icon icon__info"></i>Requires a connection to a supported issue service (e.g.
204+
GitHub)
205+
</p>
206+
</div>
207+
197208
<div class="setting">
198209
<div class="setting__input">
199210
<input

0 commit comments

Comments
 (0)