Skip to content

Commit

Permalink
Improves overview branch autolinks
Browse files Browse the repository at this point in the history
- skip redirected PR autolinks if the refset is non-prefixed
- filter autolinks by type=issue before render in the issues section
- add unlink feature
  • Loading branch information
nzaytsev committed Jan 20, 2025
1 parent 960b7a3 commit 669de27
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 29 deletions.
9 changes: 8 additions & 1 deletion src/autolinks/autolinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export class Autolinks implements Disposable {
linkIntegration = undefined;
}
}
const issueOrPullRequestPromise =
let issueOrPullRequestPromise =
remote?.provider != null &&
integration != null &&
link.provider?.id === integration.id &&
Expand All @@ -235,6 +235,13 @@ export class Autolinks implements Disposable {
: link.descriptor != null
? linkIntegration?.getIssueOrPullRequest(link.descriptor, this.getAutolinkEnrichableId(link))
: undefined;
// we consider that all non-prefixed links are came from branch names and linked to issues
// skip if it's a PR link
if (!link.prefix) {
issueOrPullRequestPromise = issueOrPullRequestPromise?.then(x =>
x?.type === 'pullrequest' ? undefined : x,
);
}
enrichedAutolinks.set(id, [issueOrPullRequestPromise, link]);
}

Expand Down
1 change: 1 addition & 0 deletions src/constants.commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ export type TreeViewCommandSuffixesByViewType<T extends TreeViewTypes> = Extract
>;

type HomeWebviewCommands = `home.${
| 'unlinkIssue'
| 'openMergeTargetComparison'
| 'openPullRequestChanges'
| 'openPullRequestComparison'
Expand Down
4 changes: 4 additions & 0 deletions src/constants.storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export type GlobalStorage = {
'graph:searchMode': StoredGraphSearchMode;
'views:scm:grouped:welcome:dismissed': boolean;
'integrations:configured': StoredIntegrationConfigurations;
'autolinks:branches:ignore': IgnoredBranchesAutolinks;
'autolinks:branches:ignore:skipPrompt': boolean | undefined;
} & { [key in `plus:preview:${FeaturePreviews}:usages`]: StoredFeaturePreviewUsagePeriod[] } & {
[key in `confirm:ai:tos:${AIProviders}`]: boolean;
} & {
Expand All @@ -91,6 +93,8 @@ export type GlobalStorage = {

export type StoredIntegrationConfigurations = Record<string, StoredConfiguredIntegrationDescriptor[] | undefined>;

export type IgnoredBranchesAutolinks = Record<string, string[] | undefined>;

export interface StoredConfiguredIntegrationDescriptor {
cloud: boolean;
integrationId: IntegrationId;
Expand Down
12 changes: 11 additions & 1 deletion src/git/models/branch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { Container } from '../../container';
import { formatDate, fromNow } from '../../system/date';
import { debug } from '../../system/decorators/log';
import { memoize } from '../../system/decorators/memoize';
import { forEach } from '../../system/iterable';
import { getLoggableName } from '../../system/logger';
import {
formatDetachedHeadName,
Expand Down Expand Up @@ -127,9 +128,18 @@ export class GitBranch implements GitBranchReference {
}

@memoize()
async getEnrichedAutolinks(): Promise<Map<string, EnrichedAutolink> | undefined> {
async getEnrichedAutolinks(ignoredLinks?: string[]): Promise<Map<string, EnrichedAutolink> | undefined> {
const remote = await this.container.git.remotes(this.repoPath).getBestRemoteWithProvider();
const branchAutolinks = await this.container.autolinks.getBranchAutolinks(this.name, remote);
if (ignoredLinks?.length) {
const ignoredMap = Object.fromEntries(ignoredLinks.map(x => [x, true]));
forEach(branchAutolinks, ([key, link]) => {
if (ignoredMap[link.url]) {
console.log('ignored', link.url, branchAutolinks);
branchAutolinks.delete(key);
}
});
}
return this.container.autolinks.getEnrichedAutolinks(branchAutolinks, remote);
}

Expand Down
61 changes: 54 additions & 7 deletions src/webviews/apps/plus/home/components/branch-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { AssociateIssueWithBranchCommandArgs } from '../../../../../plus/st
import { createCommandLink } from '../../../../../system/commands';
import { fromNow } from '../../../../../system/date';
import { interpolate, pluralize } from '../../../../../system/string';
import type { BranchRef, GetOverviewBranch, OpenInGraphParams } from '../../../../home/protocol';
import type { BranchRef, GetOverviewBranch, OpenInGraphParams, OverviewBranchIssue } from '../../../../home/protocol';
import { renderBranchName } from '../../../shared/components/branch-name';
import type { GlCard } from '../../../shared/components/card/card';
import { GlElement, observe } from '../../../shared/components/element';
Expand Down Expand Up @@ -58,6 +58,25 @@ export const branchCardStyles = css`
flex-direction: column;
gap: 0.4rem;
}
.branch-item__unplug {
padding: 0.2em;
margin-block: -0.2em;
opacity: 0;
border-radius: 3px;
}
.branch-item__section:hover .branch-item__unplug,
.branch-item__section:focus-within .branch-item__unplug {
opacity: 1;
}
.branch-item__unplug:hover,
.branch-item__unplug:focus {
background-color: var(--vscode-toolbar-hoverBackground);
outline: 1px dashed var(--vscode-toolbar-hoverOutline);
}
.branch-item__section > * {
margin-block: 0;
}
Expand Down Expand Up @@ -499,19 +518,47 @@ export abstract class GlBranchCardBase extends GlElement {
this.toggleExpanded(true);
}

protected renderIssues() {
private getIssues() {
const { autolinks, issues } = this;
const issuesSource = issues?.length ? issues : autolinks;
if (!issuesSource?.length) return nothing;
const issuesMap: Record<string, OverviewBranchIssue> = {};
autolinks?.map(autolink => {
if (autolink.type !== 'issue') {
return;
}
issuesMap[autolink.url] = autolink;
});
issues?.map(issue => {
issuesMap[issue.url] = issue;
});
return Object.values(issuesMap);
}

protected renderIssues(issues: OverviewBranchIssue[]) {
if (!issues.length) return nothing;
return html`
${issuesSource.map(issue => {
${issues.map(issue => {
return html`
<p class="branch-item__grouping">
<span class="branch-item__icon">
<issue-icon state=${issue.state} issue-id=${issue.id}></issue-icon>
</span>
<a href=${issue.url} class="branch-item__name branch-item__name--secondary">${issue.title}</a>
${when(
issue.isAutolink && this.expanded,
() => html`
<gl-tooltip>
<a
class="branch-item__unplug"
href=${createCommandLink('gitlens.home.unlinkIssue', {
issue: issue,
reference: this.branch.reference,
})}
><code-icon icon="gl-unplug"></code-icon
></a>
<div slot="content">Unlink automatically linked issue</div>
</gl-tooltip>
`,
)}
<span class="branch-item__identifier">#${issue.id}</span>
</p>
`;
Expand Down Expand Up @@ -791,7 +838,7 @@ export abstract class GlBranchCardBase extends GlElement {
}

protected renderIssuesItem() {
const issues = [...(this.issues ?? []), ...(this.autolinks ?? [])];
const issues = this.getIssues();
if (!issues.length) {
if (!this.expanded) return nothing;

Expand Down Expand Up @@ -821,7 +868,7 @@ export abstract class GlBranchCardBase extends GlElement {

return html`
<gl-work-item ?expanded=${this.expanded} ?nested=${!this.branch.opened} .indicator=${indicator}>
<div class="branch-item__section">${this.renderIssues()}</div>
<div class="branch-item__section">${this.renderIssues(issues)}</div>
</gl-work-item>
`;
}
Expand Down
42 changes: 39 additions & 3 deletions src/webviews/home/homeWebview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import type { Issue } from '../../git/models/issue';
import type { GitPausedOperationStatus } from '../../git/models/pausedOperationStatus';
import type { PullRequest } from '../../git/models/pullRequest';
import { getComparisonRefsForPullRequest } from '../../git/models/pullRequest';
import type { GitBranchReference } from '../../git/models/reference';
import { getReferenceFromBranch } from '../../git/models/reference.utils';
import { RemoteResourceType } from '../../git/models/remoteResource';
import type { Repository } from '../../git/models/repository';
Expand Down Expand Up @@ -68,6 +69,7 @@ import type {
GetOverviewResponse,
IntegrationState,
OpenInGraphParams,
OverviewBranchIssue,
OverviewFilters,
OverviewRecentThreshold,
OverviewStaleThreshold,
Expand Down Expand Up @@ -312,6 +314,7 @@ export class HomeWebviewProvider implements WebviewProvider<State, State, HomeWe
registerCommand('gitlens.home.continuePausedOperation', this.continuePausedOperation, this),
registerCommand('gitlens.home.abortPausedOperation', this.abortPausedOperation, this),
registerCommand('gitlens.home.openRebaseEditor', this.openRebaseEditor, this),
registerCommand('gitlens.home.unlinkIssue', this.unlinkIssue, this),
];
}

Expand Down Expand Up @@ -504,6 +507,35 @@ export class HomeWebviewProvider implements WebviewProvider<State, State, HomeWe
});
}

private async unlinkIssue({ issue, reference }: { reference: GitBranchReference; issue: OverviewBranchIssue }) {
const skipPrompt = this.container.storage.get('autolinks:branches:ignore:skipPrompt') || undefined;
const item =
skipPrompt ??
(await window.showWarningMessage(
`This action will unlink the issue ${issue.url} from the branch ${reference.name} forever`,
{
modal: true,
},
`OK`,
`OK, Don't ask again`,
));
if (!item) {
return;
}
if (item === `OK, Don't ask again`) {
void this.container.storage.store('autolinks:branches:ignore:skipPrompt', true);
}
const prev = this.container.storage.get('autolinks:branches:ignore') ?? {};
const refId = reference.id ?? `${reference.repoPath}/${reference.remote}/${reference.ref}`;
await this.container.storage
.store('autolinks:branches:ignore', {
...prev,
[refId]: [...(prev[refId] ?? []), issue.url],
})
.catch();
void this.host.notify(DidChangeRepositoryWip, undefined);
}

private async createCloudPatch(ref: BranchRef) {
const status = await this.container.git.status(ref.repoPath).getStatus();
if (status == null) return;
Expand Down Expand Up @@ -1192,12 +1224,13 @@ function getOverviewBranches(
const wt = worktreesByBranch.get(branch.id);
const worktree: GetOverviewBranch['worktree'] = wt ? { name: wt.name, uri: wt.uri.toString() } : undefined;

const ignored = container.storage.get('autolinks:branches:ignore')?.[branch.id];
const timestamp = branch.date?.getTime();
if (branch.current || wt?.opened) {
const forceOptions = options?.forceActive ? { force: true } : undefined;
if (options?.isPro !== false) {
prPromises.set(branch.id, getPullRequestInfo(container, branch, launchpadPromise));
autolinkPromises.set(branch.id, branch.getEnrichedAutolinks());
autolinkPromises.set(branch.id, branch.getEnrichedAutolinks(ignored));
issuePromises.set(
branch.id,
getAssociatedIssuesForBranch(container, branch).then(issues => issues.value),
Expand Down Expand Up @@ -1239,7 +1272,7 @@ function getOverviewBranches(
if (timestamp != null && timestamp > recentThreshold) {
if (options?.isPro !== false) {
prPromises.set(branch.id, getPullRequestInfo(container, branch, launchpadPromise));
autolinkPromises.set(branch.id, branch.getEnrichedAutolinks());
autolinkPromises.set(branch.id, branch.getEnrichedAutolinks(ignored));
issuePromises.set(
branch.id,
getAssociatedIssuesForBranch(container, branch).then(issues => issues.value),
Expand Down Expand Up @@ -1288,7 +1321,8 @@ function getOverviewBranches(
}

if (options?.isPro !== false) {
autolinkPromises.set(branch.id, branch.getEnrichedAutolinks());
const ignored = container.storage.get('autolinks:branches:ignore')?.[branch.id];
autolinkPromises.set(branch.id, branch.getEnrichedAutolinks(ignored));
issuePromises.set(
branch.id,
getAssociatedIssuesForBranch(container, branch).then(issues => issues.value),
Expand Down Expand Up @@ -1406,6 +1440,8 @@ async function getAutolinkIssuesInfo(links: Map<string, EnrichedAutolink> | unde
title: issue.title,
url: issue.url,
state: issue.state,
type: issue.type,
isAutolink: true,
};
}),
);
Expand Down
28 changes: 11 additions & 17 deletions src/webviews/home/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IntegrationDescriptor } from '../../constants.integrations';
import type { GitBranchMergedStatus } from '../../git/gitProvider';
import type { GitBranchStatus, GitTrackingState } from '../../git/models/branch';
import type { Issue } from '../../git/models/issue';
import type { Issue, IssueOrPullRequestType } from '../../git/models/issue';
import type { MergeConflict } from '../../git/models/mergeConflict';
import type { GitPausedOperationStatus } from '../../git/models/pausedOperationStatus';
import type { GitBranchReference } from '../../git/models/reference';
Expand Down Expand Up @@ -67,6 +67,14 @@ export interface GetOverviewRequest {
[key: string]: unknown;
}

export interface OverviewBranchIssue {
id: string;
title: string;
url: string;
state: Omit<Issue['state'], 'merged'>;
isAutolink?: boolean;
}

export interface GetOverviewBranch {
reference: GitBranchReference;

Expand Down Expand Up @@ -166,23 +174,9 @@ export interface GetOverviewBranch {
| undefined
>;

autolinks?: Promise<
{
id: string;
title: string;
url: string;
state: Omit<Issue['state'], 'merged'>;
}[]
>;
autolinks?: Promise<(OverviewBranchIssue & { type: IssueOrPullRequestType })[]>;

issues?: Promise<
{
id: string;
title: string;
url: string;
state: Omit<Issue['state'], 'merged'>;
}[]
>;
issues?: Promise<OverviewBranchIssue[]>;

worktree?: {
name: string;
Expand Down

0 comments on commit 669de27

Please sign in to comment.