@@ -19,6 +19,7 @@ import type {
1919} from '../../../config' ;
2020import { GlyphChars } from '../../../constants' ;
2121import { GlCommand } from '../../../constants.commands' ;
22+ import { HostingIntegrationId , IssueIntegrationId } from '../../../constants.integrations' ;
2223import type { StoredGraphFilters , StoredGraphRefType } from '../../../constants.storage' ;
2324import type { GraphShownTelemetryContext , GraphTelemetryContext , TelemetryEvents } from '../../../constants.telemetry' ;
2425import type { Container } from '../../../container' ;
@@ -50,6 +51,7 @@ import { GitSearchError } from '../../../git/errors';
5051import { CommitFormatter } from '../../../git/formatters/commitFormatter' ;
5152import type { GitBranch } from '../../../git/models/branch' ;
5253import {
54+ getAssociatedIssuesForBranch ,
5355 getBranchId ,
5456 getBranchNameWithoutRemote ,
5557 getDefaultBranchName ,
@@ -62,6 +64,7 @@ import { isStash } from '../../../git/models/commit';
6264import { splitCommitMessage } from '../../../git/models/commit.utils' ;
6365import { GitContributor } from '../../../git/models/contributor' ;
6466import type { GitGraph , GitGraphRowType } from '../../../git/models/graph' ;
67+ import type { IssueShape } from '../../../git/models/issue' ;
6568import type { PullRequest } from '../../../git/models/pullRequest' ;
6669import {
6770 getComparisonRefsForPullRequest ,
@@ -113,7 +116,7 @@ import {
113116import { configuration } from '../../../system/vscode/configuration' ;
114117import { getContext , onDidChangeContext } from '../../../system/vscode/context' ;
115118import 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' ;
117120import { isWebviewItemContext , isWebviewItemGroupContext , serializeWebviewItemContext } from '../../../system/webview' ;
118121import { DeepLinkActionType } from '../../../uris/deepLinks/deepLink' ;
119122import { 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 > ;
40194106function 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+ }
0 commit comments