diff --git a/package.json b/package.json index 47c3014..03ba114 100644 --- a/package.json +++ b/package.json @@ -6431,6 +6431,18 @@ "icon": "$(git-pull-request-create)" }, { + "command": "gitlens.graph.openPullRequestOnRemote", + "title": "Open Pull Request on Remote", + "category": "GitLens", + "icon": "$(globe)" + }, + { + "command": "gitlens.graph.copyRemotePullRequestUrl", + "title": "Copy Pull Request Url", + "category": "GitLens", + "icon": "$(copy)" + }, + { "command": "gitlens.graph.columnAuthorOn", "title": "Show Author", "category": "GitLens" @@ -8375,6 +8387,14 @@ "when": "false" }, { + "command": "gitlens.graph.openPullRequestOnRemote", + "when": "false" + }, + { + "command": "gitlens.graph.copyRemotePullRequestUrl", + "when": "false" + }, + { "command": "gitlens.graph.columnAuthorOn", "when": "false" }, @@ -10988,6 +11008,16 @@ "group": "1_gitlens_actions@3" }, { + "command": "gitlens.graph.openPullRequestOnRemote", + "when": "webviewItem =~ /gitlens:graph:pullrequest\\b/", + "group": "1_gitlens_actions@1" + }, + { + "command": "gitlens.graph.copyRemotePullRequestUrl", + "when": "webviewItem =~ /gitlens:graph:pullrequest\\b/", + "group": "7_gitlens_cutcopypaste@1" + }, + { "command": "gitlens.graph.columnAuthorOn", "when": "webviewItem =~ /gitlens:graph:columns\\b/ && webviewItemValue =~ /\\bauthor\\b/", "group": "1_columns@1" diff --git a/src/env/node/git/localGitProvider.ts b/src/env/node/git/localGitProvider.ts index 60ceac2..5b535ae 100644 --- a/src/env/node/git/localGitProvider.ts +++ b/src/env/node/git/localGitProvider.ts @@ -14,9 +14,8 @@ import type { } from '../../../@types/vscode.git'; import { getCachedAvatarUri } from '../../../avatars'; import { configuration } from '../../../configuration'; -import { ContextKeys, CoreGitConfiguration, GlyphChars, Schemes } from '../../../constants'; +import { CoreGitConfiguration, GlyphChars, Schemes } from '../../../constants'; import type { Container } from '../../../container'; -import { getContext } from '../../../context'; import { emojify } from '../../../emojis'; import { Features } from '../../../features'; import { GitErrorHandling } from '../../../git/commandOptions'; @@ -48,6 +47,7 @@ import { encodeGitLensRevisionUriAuthority, GitUri } from '../../../git/gitUri'; import type { GitBlame, GitBlameAuthor, GitBlameLine, GitBlameLines } from '../../../git/models/blame'; import type { BranchSortOptions } from '../../../git/models/branch'; import { + getBranchId, getBranchNameWithoutRemote, getRemoteNameFromBranchName, GitBranch, @@ -62,7 +62,6 @@ import type { GitFile, GitFileStatus } from '../../../git/models/file'; import { GitFileChange } from '../../../git/models/file'; import type { GitGraph, - GitGraphRefMetadata, GitGraphRow, GitGraphRowContexts, GitGraphRowHead, @@ -1662,9 +1661,7 @@ export class LocalGitProvider implements GitProvider, Disposable { ); } - const hasConnectedRemotes = getContext(ContextKeys.HasConnectedRemotes); const avatars = new Map(); - const refMetadata = hasConnectedRemotes ? new Map() : undefined; const ids = new Set(); const reachableFromHEAD = new Set(); const skippedIds = new Set(); @@ -1710,7 +1707,7 @@ export class LocalGitProvider implements GitProvider, Disposable { if (cursorIndex === -1) { // If we didn't find any new commits, we must have them all so return that we have everything if (size === data.length) { - return { repoPath: repoPath, avatars: avatars, ids: ids, refMetadata: refMetadata, rows: [] }; + return { repoPath: repoPath, avatars: avatars, ids: ids, rows: [] }; } size = data.length; @@ -1736,7 +1733,7 @@ export class LocalGitProvider implements GitProvider, Disposable { } } - if (!data) return { repoPath: repoPath, avatars: avatars, ids: ids, refMetadata: refMetadata, rows: [] }; + if (!data) return { repoPath: repoPath, avatars: avatars, ids: ids, rows: [] }; log = data; if (limit !== 0) { @@ -1816,7 +1813,6 @@ export class LocalGitProvider implements GitProvider, Disposable { } } - branch = branchMap.get(tip); remoteName = getRemoteNameFromBranchName(tip); if (remoteName) { remote = remoteMap.get(remoteName); @@ -1824,8 +1820,9 @@ export class LocalGitProvider implements GitProvider, Disposable { branchName = getBranchNameWithoutRemote(tip); if (branchName === 'HEAD') continue; + const remoteBranchId = getBranchId(repoPath, true, tip); refRemoteHeads.push({ - id: branch?.id ?? remote.id, + id: remoteBranchId, name: branchName, owner: remote.name, url: remote.url, @@ -1851,7 +1848,11 @@ export class LocalGitProvider implements GitProvider, Disposable { } } + branch = branchMap.get(tip); + const branchId = branch?.id ?? getBranchId(repoPath, false, tip); + refHeads.push({ + id: branchId, name: tip, isCurrentHead: current, context: serializeWebviewItemContext({ @@ -1968,7 +1969,6 @@ export class LocalGitProvider implements GitProvider, Disposable { avatars: avatars, ids: ids, skippedIds: skippedIds, - refMetadata: refMetadata, rows: rows, id: sha, diff --git a/src/git/models/branch.ts b/src/git/models/branch.ts index 11d8a1e..fc71228 100644 --- a/src/git/models/branch.ts +++ b/src/git/models/branch.ts @@ -38,6 +38,10 @@ export interface BranchSortOptions { orderBy?: BranchSorting; } +export function getBranchId(repoPath: string, remote: boolean, name: string): string { + return `${repoPath}|${remote ? 'remotes/' : 'heads/'}${name}`; +} + export class GitBranch implements GitBranchReference { readonly refType = 'branch'; readonly detached: boolean; @@ -58,7 +62,7 @@ export class GitBranch implements GitBranchReference { detached: boolean = false, public readonly rebasing: boolean = false, ) { - this.id = `${repoPath}|${remote ? 'remotes/' : 'heads/'}${name}`; + this.id = getBranchId(repoPath, remote, name); this.detached = detached || (this.current ? isDetachedHead(name) : false); if (this.detached) { diff --git a/src/git/models/graph.ts b/src/git/models/graph.ts index 18f992b..c7b365a 100644 --- a/src/git/models/graph.ts +++ b/src/git/models/graph.ts @@ -1,11 +1,9 @@ -import type { GraphRow, Head, HostingServiceType, RefMetadata, Remote, RowContexts, Tag } from '@gitkraken/gitkraken-components'; +import type { GraphRow, Head, Remote, RowContexts, Tag } from '@gitkraken/gitkraken-components'; export type GitGraphRowHead = Head; export type GitGraphRowRemoteHead = Remote; export type GitGraphRowTag = Tag; export type GitGraphRowContexts = RowContexts; -export type GitGraphRefMetadata = RefMetadata; -export type GitGraphHostingServiceType = HostingServiceType; export const enum GitGraphRowType { Commit = 'commit-node', MergeCommit = 'merge-node', @@ -27,8 +25,6 @@ export interface GitGraph { readonly repoPath: string; /** A map of all avatar urls */ readonly avatars: Map; - /** A map of all ref metadata */ - readonly refMetadata: Map | undefined; /** A set of all "seen" commit ids */ readonly ids: Set; /** A set of all skipped commit ids -- typically for stash index/untracked commits */ diff --git a/src/plus/github/githubGitProvider.ts b/src/plus/github/githubGitProvider.ts index 09ff0af..3ddde47 100644 --- a/src/plus/github/githubGitProvider.ts +++ b/src/plus/github/githubGitProvider.ts @@ -14,7 +14,7 @@ import { encodeUtf8Hex } from '@env/hex'; import { configuration } from '../../configuration'; import { CharCode, ContextKeys, Schemes } from '../../constants'; import type { Container } from '../../container'; -import { getContext, setContext } from '../../context'; +import { setContext } from '../../context'; import { emojify } from '../../emojis'; import { AuthenticationError, @@ -39,7 +39,7 @@ import { GitProviderId, RepositoryVisibility } from '../../git/gitProvider'; import { GitUri } from '../../git/gitUri'; import type { GitBlame, GitBlameAuthor, GitBlameLine, GitBlameLines } from '../../git/models/blame'; import type { BranchSortOptions } from '../../git/models/branch'; -import { GitBranch, sortBranches } from '../../git/models/branch'; +import { getBranchId, GitBranch, sortBranches } from '../../git/models/branch'; import type { GitCommitLine } from '../../git/models/commit'; import { GitCommit, GitCommitIdentity } from '../../git/models/commit'; import { GitContributor } from '../../git/models/contributor'; @@ -48,7 +48,6 @@ import type { GitFile } from '../../git/models/file'; import { GitFileChange, GitFileIndexStatus } from '../../git/models/file'; import type { GitGraph, - GitGraphRefMetadata, GitGraphRow, GitGraphRowHead, GitGraphRowRemoteHead, @@ -57,7 +56,6 @@ import type { import { GitGraphRowType } from '../../git/models/graph'; import type { GitLog } from '../../git/models/log'; import type { GitMergeStatus } from '../../git/models/merge'; -import type { PullRequest } from '../../git/models/pullRequest'; import type { GitRebaseStatus } from '../../git/models/rebase'; import type { GitBranchReference, GitReference } from '../../git/models/reference'; import { GitRevision } from '../../git/models/reference'; @@ -1085,9 +1083,7 @@ export class GitHubGitProvider implements GitProvider, Disposable { this.getTags(repoPath), ]); - const hasConnectedRemotes = getContext(ContextKeys.HasConnectedRemotes); const avatars = new Map(); - const refMetadata = hasConnectedRemotes ? new Map() : undefined; const ids = new Set(); return this.getCommitsForGraphCore( @@ -1098,7 +1094,6 @@ export class GitHubGitProvider implements GitProvider, Disposable { getSettledValue(remotesResult)?.[0], getSettledValue(tagsResult)?.values, avatars, - refMetadata, ids, options, ); @@ -1112,7 +1107,6 @@ export class GitHubGitProvider implements GitProvider, Disposable { remote: GitRemote | undefined, tags: GitTag[] | undefined, avatars: Map, - refMetadata: Map | undefined, ids: Set, options?: { branch?: string; @@ -1125,7 +1119,6 @@ export class GitHubGitProvider implements GitProvider, Disposable { return { repoPath: repoPath, avatars: avatars, - refMetadata: refMetadata, ids: ids, rows: [], }; @@ -1136,7 +1129,6 @@ export class GitHubGitProvider implements GitProvider, Disposable { return { repoPath: repoPath, avatars: avatars, - refMetadata: refMetadata, ids: ids, rows: [], }; @@ -1156,13 +1148,14 @@ export class GitHubGitProvider implements GitProvider, Disposable { if (hasHeadShaAndRemote && commit.sha === branch.sha) { refHeads = [ { + id: getBranchId(repoPath, false, branch.name), name: branch.name, isCurrentHead: true, }, ]; refRemoteHeads = [ { - id: remote.id, + id: getBranchId(repoPath, true, branch.name), name: branch.name, owner: remote.name, url: remote.url, @@ -1222,7 +1215,6 @@ export class GitHubGitProvider implements GitProvider, Disposable { return { repoPath: repoPath, avatars: avatars, - refMetadata: refMetadata, ids: ids, rows: rows, id: options?.ref, @@ -1242,7 +1234,6 @@ export class GitHubGitProvider implements GitProvider, Disposable { remote, tags, avatars, - refMetadata, ids, options, ); diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index 78fae1d..c8824dd 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -22,6 +22,7 @@ import type { CopyShaToClipboardCommandArgs, OpenBranchOnRemoteCommandArgs, OpenCommitOnRemoteCommandArgs, + OpenPullRequestOnRemoteCommandArgs, ShowCommitsInViewCommandArgs, } from '../../../commands'; import { parseCommandContext } from '../../../commands/base'; @@ -32,10 +33,9 @@ import type { Container } from '../../../container'; import { getContext, onDidChangeContext, setContext } from '../../../context'; import { PlusFeatures } from '../../../features'; import { GitSearchError } from '../../../git/errors'; -import type { GitBranch } from '../../../git/models/branch'; import type { GitCommit } from '../../../git/models/commit'; import { GitGraphRowType } from '../../../git/models/graph'; -import type { GitGraph, GitGraphHostingServiceType } from '../../../git/models/graph'; +import type { GitGraph } from '../../../git/models/graph'; import type { GitBranchReference, GitRevisionReference, @@ -77,12 +77,16 @@ import type { DismissBannerParams, EnsureRowParams, GetMissingAvatarsParams, - GetMissingRefMetadataParams, + GetMissingRefsMetadataParams, GetMoreRowsParams, GraphColumnConfig, GraphColumnName, GraphColumnsSettings, GraphComponentConfig, + GraphHostingServiceType, + GraphMissingRefsMetadataType, + GraphPullRequestMetadata, + GraphRefMetadata, GraphRepository, GraphSelectedRows, GraphWorkingTreeStats, @@ -98,7 +102,7 @@ import { DidChangeColumnsNotificationType, DidChangeGraphConfigurationNotificationType, DidChangeNotificationType, - DidChangeRefMetadataNotificationType, + DidChangeRefsMetadataNotificationType, DidChangeRowsNotificationType, DidChangeSelectionNotificationType, DidChangeSubscriptionNotificationType, @@ -108,7 +112,7 @@ import { DismissBannerCommandType, EnsureRowCommandType, GetMissingAvatarsCommandType, - GetMissingRefMetadataCommandType, + GetMissingRefsMetadataCommandType, GetMoreRowsCommandType, SearchCommandType, SearchOpenInViewCommandType, @@ -158,6 +162,12 @@ export class GraphWebview extends WebviewBase { value.onDidChange(this.onRepositoryChanged, this), value.startWatchingFileSystem(), value.onDidChangeFileSystem(this.onRepositoryFileSystemChanged, this), + onDidChangeContext(key => { + if (key !== ContextKeys.HasConnectedRemotes) return; + + this.resetRefsMetadata(); + this.updateRefsMetadata(); + }), ); } @@ -175,6 +185,7 @@ export class GraphWebview extends WebviewBase { private _etagRepository?: number; private _graph?: GitGraph; private _pendingIpcNotifications = new Map Promise)>(); + private _refsMetadata: Map | null | undefined; private _search: GitSearch | undefined; private _searchCancellation: CancellationTokenSource | undefined; private _selectedId?: string; @@ -262,7 +273,7 @@ export class GraphWebview extends WebviewBase { this.repository = context.node.repo; } - if (this.repository != null) { + if (this.repository != null && this.isReady) { this.updateState(); } } @@ -318,6 +329,12 @@ export class GraphWebview extends WebviewBase { registerCommand('gitlens.graph.createWorktree', this.createWorktree, this), registerCommand('gitlens.graph.createPullRequest', this.createPullRequest, this), + registerCommand('gitlens.graph.openPullRequestOnRemote', this.openPullRequestOnRemote, this), + registerCommand( + 'gitlens.graph.copyRemotePullRequestUrl', + item => this.openPullRequestOnRemote(item, true), + this, + ), registerCommand('gitlens.graph.copyMessage', this.copyMessage, this), registerCommand('gitlens.graph.copySha', this.copySha, this), @@ -355,8 +372,8 @@ export class GraphWebview extends WebviewBase { case GetMissingAvatarsCommandType.method: onIpc(GetMissingAvatarsCommandType, e, params => this.onGetMissingAvatars(params)); break; - case GetMissingRefMetadataCommandType.method: - onIpc(GetMissingRefMetadataCommandType, e, params => this.onGetMissingRefMetadata(params)); + case GetMissingRefsMetadataCommandType.method: + onIpc(GetMissingRefsMetadataCommandType, e, params => this.onGetMissingRefMetadata(params)); break; case GetMoreRowsCommandType.method: onIpc(GetMoreRowsCommandType, e, params => this.onGetMoreRows(params)); @@ -542,41 +559,60 @@ export class GraphWebview extends WebviewBase { } } - private async onGetMissingRefMetadata(e: GetMissingRefMetadataParams) { - if (this._graph == null) return; + private async onGetMissingRefMetadata(e: GetMissingRefsMetadataParams) { + if (this._graph == null || this._refsMetadata === null || !getContext(ContextKeys.HasConnectedRemotes)) return; + const repoPath = this._graph.repoPath; - const hasConnectedRemotes = getContext(ContextKeys.HasConnectedRemotes); - if (!hasConnectedRemotes) return; - - async function getRefMetadata(this: GraphWebview, id: string, type: string) { - const newRefMetadata = { ...(this._graph!.refMetadata ? this._graph!.refMetadata.get(id) : undefined) }; - const foundBranchesResult = await this.container.git.getBranches(repoPath, { filter: b => b.id === id }); - const foundBranches = foundBranchesResult?.values; - const refBranch: GitBranch | undefined = foundBranches?.length ? foundBranches[0] : undefined; - switch (type) { - case 'pullRequests': - newRefMetadata.pullRequests = null; - if (refBranch != null) { - const pullRequest = await refBranch.getAssociatedPullRequest(); - if (pullRequest != null) { - const pullRequestMetadata = { - hostingServiceType: pullRequest.provider.name as GitGraphHostingServiceType, - id: Number.parseInt(pullRequest.id) || 0, - title: pullRequest.title - }; - newRefMetadata.pullRequests = [ pullRequestMetadata ]; - } - } - break; - default: - break; + + async function getRefMetadata(this: GraphWebview, id: string, type: GraphMissingRefsMetadataType) { + if (this._refsMetadata == null) { + this._refsMetadata = new Map(); } - this._graph!.refMetadata?.set(id, newRefMetadata); + + const metadata = { ...this._refsMetadata.get(id) }; + if (type !== 'pullRequests') { + (metadata as any)[type] = null; + this._refsMetadata.set(id, metadata); + return; + } + + const branch = (await this.container.git.getBranches(repoPath, { filter: b => b.id === id && b.remote })) + ?.values?.[0]; + const pr = await branch?.getAssociatedPullRequest(); + if (pr == null) { + if (metadata.pullRequests === undefined || metadata.pullRequests?.length === 0) { + metadata.pullRequests = null; + } + this._refsMetadata.set(id, metadata); + return; + } + + const prMetadata: GraphPullRequestMetadata = { + // TODO@eamodio: This is iffy, but works right now since `github` and `gitlab` are the only values possible currently + hostingServiceType: pr.provider.id as GraphHostingServiceType, + id: Number.parseInt(pr.id) || 0, + title: pr.title, + author: pr.author.name, + date: (pr.mergedDate ?? pr.closedDate ?? pr.date)?.getTime(), + state: pr.state, + url: pr.url, + context: serializeWebviewItemContext({ + webviewItem: 'gitlens:graph:pullrequest', + webviewItemValue: { + type: 'pullrequest', + id: pr.id, + url: pr.url, + }, + }), + }; + + metadata.pullRequests = [prMetadata]; + this._refsMetadata.set(id, metadata); } const promises: Promise[] = []; - for (const [id, missingTypes] of Object.entries(e.missing)) { + for (const [id, missingTypes] of Object.entries(e.metadata)) { for (const missingType of missingTypes) { promises.push(getRefMetadata.call(this, id, missingType)); } @@ -584,8 +620,8 @@ export class GraphWebview extends WebviewBase { if (promises.length) { await Promise.allSettled(promises); - this.updateRefMetadata(); } + this.updateRefsMetadata(); } @gate() @@ -814,30 +850,27 @@ export class GraphWebview extends WebviewBase { }); } - private _notifyDidChangeRefMetadataDebounced: Deferrable | undefined = - undefined; + private _notifyDidChangeRefsMetadataDebounced: Deferrable | undefined = + undefined; @debug() - private updateRefMetadata(immediate: boolean = false) { + private updateRefsMetadata(immediate: boolean = false) { if (immediate) { - void this.notifyDidChangeRefMetadata(); + void this.notifyDidChangeRefsMetadata(); return; } - if (this._notifyDidChangeRefMetadataDebounced == null) { - this._notifyDidChangeRefMetadataDebounced = debounce(this.notifyDidChangeRefMetadata.bind(this), 100); + if (this._notifyDidChangeRefsMetadataDebounced == null) { + this._notifyDidChangeRefsMetadataDebounced = debounce(this.notifyDidChangeRefsMetadata.bind(this), 100); } - void this._notifyDidChangeRefMetadataDebounced(); + void this._notifyDidChangeRefsMetadataDebounced(); } @debug() - private async notifyDidChangeRefMetadata() { - if (this._graph == null) return; - - const data = this._graph; - return this.notify(DidChangeRefMetadataNotificationType, { - refMetadata: data.refMetadata ? Object.fromEntries(data.refMetadata) : undefined, + private async notifyDidChangeRefsMetadata() { + return this.notify(DidChangeRefsMetadataNotificationType, { + metadata: this._refsMetadata != null ? Object.fromEntries(this._refsMetadata) : this._refsMetadata, }); } @@ -877,7 +910,7 @@ export class GraphWebview extends WebviewBase { { rows: data.rows, avatars: Object.fromEntries(data.avatars), - refMetadata: data.refMetadata != undefined ? Object.fromEntries(data.refMetadata) : undefined, + refsMetadata: this._refsMetadata != null ? Object.fromEntries(this._refsMetadata) : this._refsMetadata, selectedRows: sendSelectedRows ? this._selectedRows : undefined, paging: { startingCursor: data.paging?.startingCursor, @@ -1169,7 +1202,7 @@ export class GraphWebview extends WebviewBase { subscription: access?.subscription.current, allowed: (access?.allowed ?? false) !== false, avatars: data != null ? Object.fromEntries(data.avatars) : undefined, - refMetadata: data?.refMetadata != undefined ? Object.fromEntries(data.refMetadata) : undefined, + refsMetadata: this.resetRefsMetadata(), loading: deferRows, rows: data?.rows, paging: @@ -1196,6 +1229,11 @@ export class GraphWebview extends WebviewBase { void this.notifyDidChangeColumns(); } + private resetRefsMetadata(): null | undefined { + this._refsMetadata = getContext(ContextKeys.HasConnectedRemotes) ? undefined : null; + return this._refsMetadata; + } + private resetRepositoryState() { this.setGraph(undefined); this.setSelectedRows(undefined); @@ -1217,6 +1255,7 @@ export class GraphWebview extends WebviewBase { private setGraph(graph: GitGraph | undefined) { this._graph = graph; if (graph == null) { + this.resetRefsMetadata(); this.resetSearchState(); } } @@ -1579,6 +1618,23 @@ export class GraphWebview extends WebviewBase { } @debug() + private openPullRequestOnRemote(item: GraphItemContext, clipboard?: boolean) { + if ( + isGraphItemContext(item) && + typeof item.webviewItemValue === 'object' && + item.webviewItemValue.type === 'pullrequest' + ) { + const { url } = item.webviewItemValue; + return executeCommand(Commands.OpenPullRequestOnRemote, { + pr: { url: url }, + clipboard: clipboard, + }); + } + + return Promise.resolve(); + } + + @debug() private async toggleColumn(name: GraphColumnName, visible: boolean) { let columns = this.container.storage.getWorkspace('graph:columns'); let column = columns?.[name]; @@ -1613,7 +1669,11 @@ export type GraphItemRefContextValue = | GraphCommitContextValue | GraphStashContextValue | GraphTagContextValue; -export type GraphItemContextValue = GraphAvatarContextValue | GraphColumnsContextValue | GraphItemRefContextValue; +export type GraphItemContextValue = + | GraphAvatarContextValue + | GraphColumnsContextValue + | GraphPullRequestContextValue + | GraphItemRefContextValue; export interface GraphAvatarContextValue { type: 'avatar'; @@ -1622,6 +1682,12 @@ export interface GraphAvatarContextValue { export type GraphColumnsContextValue = string; +export interface GraphPullRequestContextValue { + type: 'pullrequest'; + id: string; + url: string; +} + export interface GraphBranchContextValue { type: 'branch'; ref: GitBranchReference; diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts index 2c969db..8e06225 100644 --- a/src/plus/webviews/graph/protocol.ts +++ b/src/plus/webviews/graph/protocol.ts @@ -4,7 +4,10 @@ import type { GraphContexts, GraphRow, GraphZoneType, + HostingServiceType, + PullRequestMetadata, RefMetadata, + RefMetadataType, Remote, WorkDirStats, } from '@gitkraken/gitkraken-components'; @@ -19,8 +22,13 @@ import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol' export type GraphColumnsSettings = Record; export type GraphSelectedRows = Record; export type GraphAvatars = Record; -export type GraphRefMetadata = Record; -export type GraphMissingRefMetadata = Record; + +export type GraphRefMetadata = RefMetadata | null; +export type GraphRefsMetadata = Record; +export type GraphHostingServiceType = HostingServiceType; +export type GraphMissingRefsMetadataType = RefMetadataType; +export type GraphMissingRefsMetadata = Record; +export type GraphPullRequestMetadata = PullRequestMetadata; export interface State { repositories?: GraphRepository[]; @@ -31,7 +39,7 @@ export interface State { allowed: boolean; avatars?: GraphAvatars; loading?: boolean; - refMetadata?: GraphRefMetadata; + refsMetadata?: GraphRefsMetadata | null; rows?: GraphRow[]; paging?: GraphPaging; columns?: GraphColumnsSettings; @@ -121,10 +129,12 @@ export interface GetMissingAvatarsParams { } export const GetMissingAvatarsCommandType = new IpcCommandType('graph/avatars/get'); -export interface GetMissingRefMetadataParams { - missing: GraphMissingRefMetadata; +export interface GetMissingRefsMetadataParams { + metadata: GraphMissingRefsMetadata; } -export const GetMissingRefMetadataCommandType = new IpcCommandType('graph/refMetadata/get'); +export const GetMissingRefsMetadataCommandType = new IpcCommandType( + 'graph/refs/metadata/get', +); export interface GetMoreRowsParams { id?: string; @@ -185,17 +195,19 @@ export const DidChangeSubscriptionNotificationType = new IpcNotificationType( 'graph/avatars/didChange', + true, ); -export interface DidChangeRefMetadataParams { - refMetadata: GraphRefMetadata | undefined; +export interface DidChangeRefsMetadataParams { + metadata: GraphRefsMetadata | null | undefined; } -export const DidChangeRefMetadataNotificationType = new IpcNotificationType( - 'graph/refMetadata/didChange', +export const DidChangeRefsMetadataNotificationType = new IpcNotificationType( + 'graph/refs/didChangeMetadata', + true, ); export interface DidChangeColumnsParams { @@ -211,7 +223,7 @@ export interface DidChangeRowsParams { rows: GraphRow[]; avatars: { [email: string]: string }; paging?: GraphPaging; - refMetadata: GraphRefMetadata | undefined; + refsMetadata?: GraphRefsMetadata | null; selectedRows?: GraphSelectedRows; } export const DidChangeRowsNotificationType = new IpcNotificationType('graph/rows/didChange'); diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx index 5929035..e7c9a0c 100644 --- a/src/webviews/apps/plus/graph/GraphWrapper.tsx +++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx @@ -16,9 +16,11 @@ import type { DidEnsureRowParams, DidSearchParams, DismissBannerParams, + GraphAvatars, GraphColumnConfig, GraphColumnName, GraphComponentConfig, + GraphMissingRefsMetadata, GraphRepository, GraphSearchResults, GraphSearchResultsError, @@ -30,7 +32,7 @@ import { DidChangeAvatarsNotificationType, DidChangeColumnsNotificationType, DidChangeGraphConfigurationNotificationType, - DidChangeRefMetadataNotificationType, + DidChangeRefsMetadataNotificationType, DidChangeRowsNotificationType, DidChangeSelectionNotificationType, DidChangeSubscriptionNotificationType, @@ -53,7 +55,7 @@ export interface GraphWrapperProps { onSelectRepository?: (repository: GraphRepository) => void; onColumnChange?: (name: GraphColumnName, settings: GraphColumnConfig) => void; onMissingAvatars?: (emails: { [email: string]: string }) => void; - onMissingRefMetadata?: (missing: { [id: string]: string[] }) => void; + onMissingRefsMetadata?: (metadata: GraphMissingRefsMetadata) => void; onMoreRows?: (id?: string) => void; onSearch?: (search: SearchQuery | undefined, options?: { limit?: number }) => void; onSearchPromise?: ( @@ -134,7 +136,7 @@ export function GraphWrapper({ onColumnChange, onEnsureRowPromise, onMissingAvatars, - onMissingRefMetadata, + onMissingRefsMetadata, onMoreRows, onSearch, onSearchPromise, @@ -151,7 +153,7 @@ export function GraphWrapper({ const [rows, setRows] = useState(state.rows ?? []); const [avatars, setAvatars] = useState(state.avatars); - const [refMetadata, setRefMetadata] = useState(state.refMetadata); + const [refsMetadata, setRefsMetadata] = useState(state.refsMetadata); const [repos, setRepos] = useState(state.repositories ?? []); const [repo, setRepo] = useState( repos.find(item => item.path === state.selectedRepository), @@ -209,8 +211,8 @@ export function GraphWrapper({ case DidChangeAvatarsNotificationType: setAvatars(state.avatars); break; - case DidChangeRefMetadataNotificationType: - setRefMetadata(state.refMetadata); + case DidChangeRefsMetadataNotificationType: + setRefsMetadata(state.refsMetadata); break; case DidChangeColumnsNotificationType: setColumns(state.columns); @@ -220,7 +222,7 @@ export function GraphWrapper({ setRows(state.rows ?? []); setSelectedRows(state.selectedRows); setAvatars(state.avatars); - setRefMetadata(state.refMetadata); + setRefsMetadata(state.refsMetadata); setPagingHasMore(state.paging?.hasMore ?? false); setIsLoading(state.loading); break; @@ -256,7 +258,7 @@ export function GraphWrapper({ setSelectedRows(state.selectedRows); setContext(state.context); setAvatars(state.avatars ?? {}); - setRefMetadata(state.refMetadata ?? {}); + setRefsMetadata(state.refsMetadata); setPagingHasMore(state.paging?.hasMore ?? false); setRepos(state.repositories ?? []); setRepo(repos.find(item => item.path === state.selectedRepository)); @@ -442,12 +444,12 @@ export function GraphWrapper({ setRepoExpanded(!repoExpanded); }; - const handleMissingAvatars = (emails: { [email: string]: string }) => { + const handleMissingAvatars = (emails: GraphAvatars) => { onMissingAvatars?.(emails); }; - const handleMissingRefMetadata = (missing: { [id: string]: string[] }) => { - onMissingRefMetadata?.(missing); + const handleMissingRefsMetadata = (metadata: GraphMissingRefsMetadata) => { + onMissingRefsMetadata?.(metadata); }; const handleToggleColumnSettings = (event: React.MouseEvent) => { @@ -701,10 +703,10 @@ export function GraphWrapper({ onColumnResized={handleOnColumnResized} onSelectGraphRows={handleSelectGraphRows} onEmailsMissingAvatarUrls={handleMissingAvatars} - onRefsMissingMetadata={handleMissingRefMetadata} + onRefsMissingMetadata={handleMissingRefsMetadata} onShowMoreCommits={handleMoreCommits} platform={clientPlatform} - refMetadataById={refMetadata} + refMetadataById={refsMetadata} shaLength={graphConfig?.idLength} themeOpacityFactor={styleProps?.themeOpacityFactor} useAuthorInitialsForAvatars={!graphConfig?.avatars} diff --git a/src/webviews/apps/plus/graph/graph.tsx b/src/webviews/apps/plus/graph/graph.tsx index 9dbd0fa..e7fbb0a 100644 --- a/src/webviews/apps/plus/graph/graph.tsx +++ b/src/webviews/apps/plus/graph/graph.tsx @@ -6,8 +6,10 @@ import type { GitGraphRowType } from '../../../../git/models/graph'; import type { SearchQuery } from '../../../../git/search'; import type { DismissBannerParams, + GraphAvatars, GraphColumnConfig, GraphColumnName, + GraphMissingRefsMetadata, GraphRepository, InternalNotificationType, State, @@ -18,7 +20,7 @@ import { DidChangeColumnsNotificationType, DidChangeGraphConfigurationNotificationType, DidChangeNotificationType, - DidChangeRefMetadataNotificationType, + DidChangeRefsMetadataNotificationType, DidChangeRowsNotificationType, DidChangeSelectionNotificationType, DidChangeSubscriptionNotificationType, @@ -28,7 +30,7 @@ import { DismissBannerCommandType, EnsureRowCommandType, GetMissingAvatarsCommandType, - GetMissingRefMetadataCommandType, + GetMissingRefsMetadataCommandType, GetMoreRowsCommandType, SearchCommandType, SearchOpenInViewCommandType, @@ -87,7 +89,7 @@ export class GraphApp extends App { 250, )} onMissingAvatars={(...params) => this.onGetMissingAvatars(...params)} - onMissingRefMetadata={(...params) => this.onGetMissingRefMetadata(...params)} + onMissingRefsMetadata={(...params) => this.onGetMissingRefsMetadata(...params)} onMoreRows={(...params) => this.onGetMoreRows(...params)} onSearch={debounce((search, options) => this.onSearch(search, options), 250)} onSearchPromise={(...params) => this.onSearchPromise(...params)} @@ -144,9 +146,9 @@ export class GraphApp extends App { }); break; - case DidChangeRefMetadataNotificationType.method: - onIpc(DidChangeRefMetadataNotificationType, msg, (params, type) => { - this.state.refMetadata = params.refMetadata; + case DidChangeRefsMetadataNotificationType.method: + onIpc(DidChangeRefsMetadataNotificationType, msg, (params, type) => { + this.state.refsMetadata = params.metadata; this.setState(this.state, type); }); break; @@ -215,7 +217,9 @@ export class GraphApp extends App { } this.state.avatars = params.avatars; - this.state.refMetadata = params.refMetadata; + if (params.refsMetadata !== undefined) { + this.state.refsMetadata = params.refsMetadata; + } this.state.rows = rows; this.state.paging = params.paging; if (params.selectedRows != null) { @@ -372,12 +376,12 @@ export class GraphApp extends App { }); } - private onGetMissingAvatars(emails: { [email: string]: string }) { + private onGetMissingAvatars(emails: GraphAvatars) { this.sendCommand(GetMissingAvatarsCommandType, { emails: emails }); } - private onGetMissingRefMetadata(missing: { [id: string]: string[] }) { - this.sendCommand(GetMissingRefMetadataCommandType, { missing: missing }); + private onGetMissingRefsMetadata(metadata: GraphMissingRefsMetadata) { + this.sendCommand(GetMissingRefsMetadataCommandType, { metadata: metadata }); } private onGetMoreRows(sha?: string) {