diff --git a/docs/telemetry-events.md b/docs/telemetry-events.md index ff5ef7cd92aaa..1d5ec05e1c666 100644 --- a/docs/telemetry-events.md +++ b/docs/telemetry-events.md @@ -2301,7 +2301,8 @@ or ```typescript { - 'step': 'welcome-in-trial' | 'welcome-paid' | 'welcome-in-trial-expired-eligible' | 'welcome-in-trial-expired' | 'get-started-community' | 'visualize-code-history' | 'accelerate-pr-reviews' | 'streamline-collaboration' | 'improve-workflows-with-integrations' + 'step': 'welcome-in-trial' | 'welcome-paid' | 'welcome-in-trial-expired-eligible' | 'welcome-in-trial-expired' | 'get-started-community' | 'visualize-code-history' | 'accelerate-pr-reviews' | 'streamline-collaboration' | 'improve-workflows-with-integrations', + 'usingFallbackUrl': boolean } ``` diff --git a/src/commands/quickCommand.steps.ts b/src/commands/quickCommand.steps.ts index c72fe714fe839..a39b38c8c7c2c 100644 --- a/src/commands/quickCommand.steps.ts +++ b/src/commands/quickCommand.steps.ts @@ -110,7 +110,6 @@ import { first, map } from '../system/iterable'; import { Logger } from '../system/logger'; import { getSettledValue } from '../system/promise'; import { pad, pluralize, truncate } from '../system/string'; -import { isWalkthroughSupported } from '../telemetry/walkthroughStateProvider'; import type { ViewsWithRepositoryFolders } from '../views/viewBase'; import type { AsyncStepResultGenerator, @@ -2688,23 +2687,21 @@ export async function* ensureAccessStep< switch (feature) { case 'launchpad': - if (isWalkthroughSupported()) { - directives.splice( - 0, - 0, - createDirectiveQuickPickItem(Directive.Cancel, undefined, { - label: 'Launchpad prioritizes your pull requests to keep you focused and your team unblocked', - detail: 'Click to learn more about Launchpad', - iconPath: new ThemeIcon('rocket'), - onDidSelect: () => - void executeCommand('gitlens.openWalkthrough', { - step: 'accelerate-pr-reviews', - source: { source: 'launchpad', detail: 'info' }, - }), - }), - createQuickPickSeparator(), - ); - } + directives.splice( + 0, + 0, + createDirectiveQuickPickItem(Directive.Cancel, undefined, { + label: 'Launchpad prioritizes your pull requests to keep you focused and your team unblocked', + detail: 'Click to learn more about Launchpad', + iconPath: new ThemeIcon('rocket'), + onDidSelect: () => + void executeCommand('gitlens.openWalkthrough', { + step: 'accelerate-pr-reviews', + source: { source: 'launchpad', detail: 'info' }, + }), + }), + createQuickPickSeparator(), + ); break; case 'startWork': directives.splice( diff --git a/src/commands/walkthroughs.ts b/src/commands/walkthroughs.ts index 4744d9f297847..3e442552d6479 100644 --- a/src/commands/walkthroughs.ts +++ b/src/commands/walkthroughs.ts @@ -1,13 +1,14 @@ import type { WalkthroughSteps } from '../constants'; import { urls } from '../constants'; import type { GlCommands } from '../constants.commands'; -import type { Source, Sources } from '../constants.telemetry'; +import type { Source, Sources, TelemetryEvents } from '../constants.telemetry'; import type { Container } from '../container'; import type { SubscriptionUpgradeCommandArgs } from '../plus/gk/models/subscription'; import type { LaunchpadCommandArgs } from '../plus/launchpad/launchpad'; import { command, executeCommand, executeCoreCommand } from '../system/-webview/command'; import { openWalkthrough as openWalkthroughCore } from '../system/-webview/vscode'; import { openUrl } from '../system/-webview/vscode/uris'; +import { isWalkthroughSupported } from '../telemetry/walkthroughStateProvider'; import type { ConnectCloudIntegrationsCommandArgs } from './cloudIntegrations'; import { GlCommandBase } from './commandBase'; import type { WorktreeGitCommandArgs } from './git/worktree'; @@ -42,9 +43,33 @@ export class OpenWalkthroughCommand extends GlCommandBase { } } +const helpCenterWalkthroughUrls = new Map([ + ['default', urls.getStarted], + ['welcome-in-trial', urls.welcomeInTrial], + ['welcome-paid', urls.welcomePaid], + ['welcome-in-trial-expired-eligible', urls.welcomeTrialReactivationEligible], + ['welcome-in-trial-expired', urls.welcomeTrialExpired], + ['get-started-community', urls.getStarted], + ['visualize-code-history', urls.interactiveCodeHistory], + ['accelerate-pr-reviews', urls.acceleratePrReviews], + ['streamline-collaboration', urls.streamlineCollaboration], + ['improve-workflows-with-integrations', urls.startIntegrations], +]); + function openWalkthrough(container: Container, args?: OpenWalkthroughCommandArgs) { + const walkthroughSupported = isWalkthroughSupported(); if (container.telemetry.enabled) { - container.telemetry.sendEvent('walkthrough', { step: args?.step }, args?.source); + const walkthroughEvent: TelemetryEvents['walkthrough'] = { step: args?.step }; + if (!walkthroughSupported) { + walkthroughEvent.usingFallbackUrl = true; + } + container.telemetry.sendEvent('walkthrough', walkthroughEvent, args?.source); + } + + if (!walkthroughSupported) { + const url = helpCenterWalkthroughUrls.get(args?.step ?? 'default'); + void openUrl(url); + return; } void openWalkthroughCore(container.context.extension.id, 'welcome', args?.step, false); diff --git a/src/constants.context.ts b/src/constants.context.ts index f95e5ded5226a..0f8b66d5a4603 100644 --- a/src/constants.context.ts +++ b/src/constants.context.ts @@ -2,11 +2,11 @@ import type { Uri } from 'vscode'; import type { AnnotationStatus, Keys } from './constants'; import type { SubscriptionState } from './constants.subscription'; import type { CustomEditorTypes, GroupableTreeViewTypes, WebviewTypes, WebviewViewTypes } from './constants.views'; +import type { WalkthroughContextKeys } from './constants.walkthroughs'; import type { Features } from './features'; import type { OrgAIProviders } from './plus/gk/models/organization'; import type { PromoKeys } from './plus/gk/models/promo'; import type { SubscriptionPlanIds } from './plus/gk/models/subscription'; -import type { WalkthroughContextKeys } from './telemetry/walkthroughStateProvider'; export type ContextKeys = { 'gitlens:debugging': boolean; diff --git a/src/constants.telemetry.ts b/src/constants.telemetry.ts index a906ed0de1c0d..d431f98e63fb9 100644 --- a/src/constants.telemetry.ts +++ b/src/constants.telemetry.ts @@ -5,12 +5,12 @@ import type { GlCommands, GlCommandsDeprecated } from './constants.commands'; import type { IntegrationIds, SupportedCloudIntegrationIds } from './constants.integrations'; import type { SubscriptionState } from './constants.subscription'; import type { CustomEditorTypes, TreeViewTypes, WebviewTypes, WebviewViewTypes } from './constants.views'; +import type { WalkthroughContextKeys } from './constants.walkthroughs'; import type { FeaturePreviews, FeaturePreviewStatus } from './features'; import type { GitContributionTiers } from './git/models/contributor'; import type { AIActionType } from './plus/ai/models/model'; import type { Subscription, SubscriptionAccount, SubscriptionStateString } from './plus/gk/models/subscription'; import type { Flatten } from './system/object'; -import type { WalkthroughContextKeys } from './telemetry/walkthroughStateProvider'; import type { GraphColumnConfig } from './webviews/plus/graph/protocol'; import type { TimelinePeriod, TimelineScopeType, TimelineSliceBy } from './webviews/plus/timeline/protocol'; @@ -963,6 +963,7 @@ interface UsageTrackEvent { interface WalkthroughEvent { step?: WalkthroughSteps; + usingFallbackUrl?: boolean; } type WalkthroughActionNames = diff --git a/src/constants.ts b/src/constants.ts index cc44edc3cc2d1..32a21fe73663c 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -188,6 +188,12 @@ export const urls = Object.freeze({ startIntegrations: `https://help.gitkraken.com/gitlens/gitlens-start-here/?${utm}#improve-workflows-with-integrations`, streamlineCollaboration: `https://help.gitkraken.com/gitlens/gitlens-start-here/?${utm}#streamline-collaboration`, aiFeatures: `https://help.gitkraken.com/gitlens/gl-gk-ai/?${utm}`, + + getStarted: `https://help.gitkraken.com/gitlens/gitlens-home/?${utm}`, + welcomeInTrial: `https://help.gitkraken.com/gitlens/gitlens-home/?${utm}`, + welcomePaid: `https://help.gitkraken.com/gitlens/gitlens-home/?${utm}`, + welcomeTrialExpired: `https://help.gitkraken.com/gitlens/gitlens-community-vs-gitlens-pro/?${utm}`, + welcomeTrialReactivationEligible: `https://help.gitkraken.com/gitlens/gitlens-community-vs-gitlens-pro/?${utm}`, }); export type WalkthroughSteps = diff --git a/src/constants.walkthroughs.ts b/src/constants.walkthroughs.ts new file mode 100644 index 0000000000000..8685c70d5935d --- /dev/null +++ b/src/constants.walkthroughs.ts @@ -0,0 +1,18 @@ +export type WalkthroughContextKeys = + | 'gettingStarted' + | 'homeView' + | 'visualizeCodeHistory' + | 'prReviews' + | 'streamlineCollaboration' + | 'integrations' + | 'aiFeatures'; + +export const walkthroughProgressSteps: WalkthroughContextKeys[] = [ + 'gettingStarted', + 'homeView', + 'visualizeCodeHistory', + 'prReviews', + 'streamlineCollaboration', + 'integrations', + 'aiFeatures', +]; diff --git a/src/container.ts b/src/container.ts index 58143fb312b0d..aa22f542721a0 100644 --- a/src/container.ts +++ b/src/container.ts @@ -59,7 +59,7 @@ import { log } from './system/decorators/log'; import { Logger } from './system/logger'; import { TelemetryService } from './telemetry/telemetry'; import { UsageTracker } from './telemetry/usageTracker'; -import { isWalkthroughSupported, WalkthroughStateProvider } from './telemetry/walkthroughStateProvider'; +import { WalkthroughStateProvider } from './telemetry/walkthroughStateProvider'; import { GitTerminalLinkProvider } from './terminal/linkProvider'; import { GitDocumentTracker } from './trackers/documentTracker'; import { LineTracker } from './trackers/lineTracker'; @@ -205,9 +205,7 @@ export class Container { ); this._disposables.push((this._uri = new UriService(this))); this._disposables.push((this._subscription = new SubscriptionService(this, this._connection, previousVersion))); - if (isWalkthroughSupported()) { - this._disposables.push((this._walkthrough = new WalkthroughStateProvider(this))); - } + this._disposables.push((this._walkthrough = new WalkthroughStateProvider(this))); this._disposables.push((this._organizations = new OrganizationService(this, this._connection))); this._disposables.push((this._git = new GitProviderService(this))); @@ -715,8 +713,8 @@ export class Container { return this._usage; } - private readonly _walkthrough: WalkthroughStateProvider | undefined; - get walkthrough(): WalkthroughStateProvider | undefined { + private readonly _walkthrough: WalkthroughStateProvider; + get walkthrough(): WalkthroughStateProvider { return this._walkthrough; } diff --git a/src/plus/gk/subscriptionService.ts b/src/plus/gk/subscriptionService.ts index 1f41dfc26ee64..47d7cac8f5faa 100644 --- a/src/plus/gk/subscriptionService.ts +++ b/src/plus/gk/subscriptionService.ts @@ -62,7 +62,6 @@ import { flatten } from '../../system/object'; import { pauseOnCancelOrTimeout } from '../../system/promise'; import { pluralize } from '../../system/string'; import { createDisposable } from '../../system/unifiedDisposable'; -import { isWalkthroughSupported } from '../../telemetry/walkthroughStateProvider'; import { LoginUriPathPrefix } from './authenticationConnection'; import { authenticationProviderScopes } from './authenticationProvider'; import type { GKCheckInResponse } from './models/checkin'; @@ -486,13 +485,13 @@ export class SubscriptionService implements Disposable { void this.resendVerification(source); } } else if (isSubscriptionPaid(this._subscription)) { - const learn: MessageItem | undefined = isWalkthroughSupported() ? { title: 'Learn More' } : undefined; + const learn: MessageItem = { title: 'Learn More' }; const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true }; const result = await window.showInformationMessage( `You are now on ${actual.name} and have full access to all GitLens Pro features.`, { modal: true }, confirm, - ...(learn ? [learn] : []), + learn, ); if (result === learn) { @@ -501,7 +500,7 @@ export class SubscriptionService implements Disposable { } else if (isSubscriptionTrial(this._subscription)) { const days = getSubscriptionTimeRemaining(this._subscription, 'days') ?? 0; - const learn: MessageItem | undefined = isWalkthroughSupported() ? { title: 'Learn More' } : undefined; + const learn: MessageItem = { title: 'Learn More' }; const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true }; const result = await window.showInformationMessage( `Welcome to your ${effective.name} Trial.\n\nYou now have full access to all GitLens Pro features for ${ @@ -512,7 +511,7 @@ export class SubscriptionService implements Disposable { detail: 'Your trial also includes access to the GitKraken DevEx platform, unleashing powerful Git visualization & productivity capabilities everywhere you work: IDE, desktop, browser, and terminal.', }, confirm, - ...(learn ? [learn] : []), + learn, ); if (result === learn) { @@ -520,9 +519,7 @@ export class SubscriptionService implements Disposable { } } else { const upgrade: MessageItem = { title: 'Upgrade to Pro' }; - const learn: MessageItem | undefined = isWalkthroughSupported() - ? { title: 'Community vs. Pro' } - : undefined; + const learn: MessageItem = { title: 'Community vs. Pro' }; const confirm: MessageItem = { title: 'Continue', isCloseAffordance: true }; const result = await window.showInformationMessage( `You are now on ${actual.name}.`, @@ -531,7 +528,7 @@ export class SubscriptionService implements Disposable { detail: 'You only have access to Pro features on publicly-hosted repos. For full access to all Pro features, please upgrade to GitLens Pro.', }, upgrade, - ...(learn ? [learn] : []), + learn, confirm, ); diff --git a/src/plus/launchpad/launchpad.ts b/src/plus/launchpad/launchpad.ts index 57114a8aeabb0..44b2f940e5292 100644 --- a/src/plus/launchpad/launchpad.ts +++ b/src/plus/launchpad/launchpad.ts @@ -53,7 +53,6 @@ import { getScopedCounter } from '../../system/counter'; import { fromNow } from '../../system/date'; import { some } from '../../system/iterable'; import { interpolate, pluralize } from '../../system/string'; -import { isWalkthroughSupported } from '../../telemetry/walkthroughStateProvider'; import { ProviderBuildStatusState, ProviderPullRequestReviewState } from '../integrations/providers/models'; import type { LaunchpadCategorizedResult, LaunchpadItem } from './launchpadProvider'; import { @@ -1176,22 +1175,21 @@ export class LaunchpadCommand extends QuickCommand { context: Context, ): AsyncStepResultGenerator<{ connected: boolean | IntegrationIds; resume: () => void | undefined }> { const hasConnectedIntegration = some(context.connectedIntegrations.values(), c => c); - const confirmations: (QuickPickItemOfT | DirectiveQuickPickItem)[] = - !hasConnectedIntegration && isWalkthroughSupported() - ? [ - createDirectiveQuickPickItem(Directive.Cancel, undefined, { - label: 'Launchpad prioritizes your pull requests to keep you focused and your team unblocked', - detail: 'Click to learn more about Launchpad', - iconPath: new ThemeIcon('rocket'), - onDidSelect: () => - void executeCommand('gitlens.openWalkthrough', { - step: 'accelerate-pr-reviews', - source: { source: 'launchpad', detail: 'info' }, - }), - }), - createQuickPickSeparator(), - ] - : []; + const confirmations: (QuickPickItemOfT | DirectiveQuickPickItem)[] = !hasConnectedIntegration + ? [ + createDirectiveQuickPickItem(Directive.Cancel, undefined, { + label: 'Launchpad prioritizes your pull requests to keep you focused and your team unblocked', + detail: 'Click to learn more about Launchpad', + iconPath: new ThemeIcon('rocket'), + onDidSelect: () => + void executeCommand('gitlens.openWalkthrough', { + step: 'accelerate-pr-reviews', + source: { source: 'launchpad', detail: 'info' }, + }), + }), + createQuickPickSeparator(), + ] + : []; for (const integration of supportedLaunchpadIntegrations) { if (context.connectedIntegrations.get(integration)) { @@ -1251,7 +1249,7 @@ export class LaunchpadCommand extends QuickCommand { const step = this.createConfirmStep( `${this.title} \u00a0\u2022\u00a0 Connect an ${hasConnectedIntegration ? 'Additional ' : ''}Integration`, [ - ...(hasConnectedIntegration || !isWalkthroughSupported() + ...(hasConnectedIntegration ? [] : [ createDirectiveQuickPickItem(Directive.Cancel, undefined, { diff --git a/src/plus/launchpad/launchpadIndicator.ts b/src/plus/launchpad/launchpadIndicator.ts index 5ae9b8488470e..59abfc6fdff39 100644 --- a/src/plus/launchpad/launchpadIndicator.ts +++ b/src/plus/launchpad/launchpadIndicator.ts @@ -11,7 +11,6 @@ import { once } from '../../system/event'; import { groupByMap } from '../../system/iterable'; import { wait } from '../../system/promise'; import { pluralize } from '../../system/string'; -import { isWalkthroughSupported } from '../../telemetry/walkthroughStateProvider'; import type { ConnectionStateChangeEvent } from '../integrations/integrationService'; import type { LaunchpadCommandArgs } from './launchpad'; import type { LaunchpadItem, LaunchpadProvider, LaunchpadRefreshEvent } from './launchpadProvider'; @@ -253,20 +252,12 @@ export class LaunchpadIndicator implements Disposable { tooltip.isTrusted = true; tooltip.appendMarkdown(`GitLens Launchpad ${proBadge}\u00a0\u00a0\u00a0\u00a0—\u00a0\u00a0\u00a0\u00a0`); - if (isWalkthroughSupported()) { - tooltip.appendMarkdown( - `[$(question)](command:gitlens.launchpad.indicator.action?%22info%22 "What is this?")`, - ); - } + tooltip.appendMarkdown(`[$(question)](command:gitlens.launchpad.indicator.action?%22info%22 "What is this?")`); tooltip.appendMarkdown('\u00a0'); tooltip.appendMarkdown(`[$(gear)](command:workbench.action.openSettings?%22gitlens.launchpad%22 "Settings")`); tooltip.appendMarkdown('\u00a0\u00a0|\u00a0\u00a0'); tooltip.appendMarkdown(`[$(circle-slash) Hide](command:gitlens.launchpad.indicator.action?%22hide%22 "Hide")`); - const launchpadLink = isWalkthroughSupported() - ? '[Launchpad](command:gitlens.launchpad.indicator.action?%22info%22 "Learn about Launchpad")' - : 'Launchpad'; - if ( state === 'idle' || state === 'disconnected' || @@ -275,7 +266,7 @@ export class LaunchpadIndicator implements Disposable { ) { tooltip.appendMarkdown('\n\n---\n\n'); tooltip.appendMarkdown( - `${launchpadLink} organizes your pull requests into actionable groups to help you focus and keep your team unblocked.`, + `[Launchpad](command:gitlens.launchpad.indicator.action?%22info%22 "Learn about Launchpad") organizes your pull requests into actionable groups to help you focus and keep your team unblocked.`, ); tooltip.appendMarkdown( "\n\nIt's always accessible using the `GitLens: Open Launchpad` command from the Command Palette.", diff --git a/src/telemetry/walkthroughStateProvider.ts b/src/telemetry/walkthroughStateProvider.ts index 690d2960d8464..afa1f4705f267 100644 --- a/src/telemetry/walkthroughStateProvider.ts +++ b/src/telemetry/walkthroughStateProvider.ts @@ -2,6 +2,7 @@ import type { Event } from 'vscode'; import { Disposable, EventEmitter } from 'vscode'; import { SubscriptionState } from '../constants.subscription'; import type { TrackedUsageKeys } from '../constants.telemetry'; +import type { WalkthroughContextKeys } from '../constants.walkthroughs'; import type { Container } from '../container'; import type { SubscriptionChangeEvent } from '../plus/gk/subscriptionService'; import { setContext } from '../system/-webview/context'; @@ -9,15 +10,6 @@ import { isCursor } from '../system/-webview/cursor'; import { wait } from '../system/promise'; import type { UsageChangeEvent } from './usageTracker'; -export type WalkthroughContextKeys = - | 'gettingStarted' - | 'homeView' - | 'visualizeCodeHistory' - | 'prReviews' - | 'streamlineCollaboration' - | 'integrations' - | 'aiFeatures'; - type WalkthroughUsage = { subscriptionStates?: SubscriptionState[] | Readonly; subscriptionCommands?: TrackedUsageKeys[] | Readonly; @@ -161,8 +153,12 @@ export class WalkthroughStateProvider implements Disposable { private readonly completed = new Set(); private subscriptionState: SubscriptionState | undefined; + readonly isWalkthroughSupported = isWalkthroughSupported(); + constructor(private readonly container: Container) { - void setContext('gitlens:walkthroughSupported', true); + if (this.isWalkthroughSupported) { + void setContext('gitlens:walkthroughSupported', true); + } this.disposables.push( this._onDidChangeProgress, @@ -272,6 +268,14 @@ export class WalkthroughStateProvider implements Disposable { return this.doneCount / this.walkthroughSize; } + getState(): Map { + const state = new Map(); + for (const key of walkthroughRequiredMapping.keys()) { + state.set(key, this.completed.has(key)); + } + return state; + } + dispose(): void { Disposable.from(...this.disposables).dispose(); } diff --git a/src/webviews/apps/home/components/onboarding.ts b/src/webviews/apps/home/components/onboarding.ts index 4ca8b4adff736..457211dd09fac 100644 --- a/src/webviews/apps/home/components/onboarding.ts +++ b/src/webviews/apps/home/components/onboarding.ts @@ -1,10 +1,11 @@ import { consume } from '@lit/context'; -import { html, LitElement } from 'lit'; +import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import type { OpenWalkthroughCommandArgs } from '../../../../commands/walkthroughs'; import { createCommandLink } from '../../../../system/commands'; import type { State } from '../../../home/protocol'; import { DismissWalkthroughSection } from '../../../home/protocol'; +import { ruleStyles } from '../../plus/shared/components/vscode.css'; import { ipcContext } from '../../shared/contexts/ipc'; import type { HostIpc } from '../../shared/ipc'; import { stateContext } from '../context'; @@ -13,9 +14,45 @@ import '../../shared/components/button'; import '../../shared/components/code-icon'; import '../../shared/components/overlays/tooltip'; +const walkthroughProgressSteps = { + gettingStarted: 'Getting Started', + homeView: 'Home View', + visualizeCodeHistory: 'Visualize Code History', + prReviews: 'PR Reviews', + streamlineCollaboration: 'Streamline Collaboration', + integrations: 'Integrations', + aiFeatures: 'AI Features', +}; + @customElement('gl-onboarding') export class GlOnboarding extends LitElement { - static override styles = [homeBaseStyles, walkthroughProgressStyles]; + static override styles = [ + homeBaseStyles, + walkthroughProgressStyles, + ruleStyles, + css` + .walkthrough-progress__label { + margin-block: 0; + } + .walkthrough-progress__steps { + margin-block: 0; + padding-inline-start: 0; + } + .walkthrough-progress__step { + list-style: none; + margin-block-start: 0.3rem; + } + .walkthrough-progress__step-label { + margin-inline-start: 0.3rem; + } + code-icon[icon='circle-large'] { + color: var(--color-foreground--50); + } + code-icon[icon='pass'] { + color: #00dd00; + } + `, + ]; @consume({ context: stateContext, subscribe: true }) @state() @@ -36,7 +73,7 @@ export class GlOnboarding extends LitElement { aria-label="Dismiss" > - + ('gitlens.openWalkthrough', { @@ -55,9 +92,33 @@ export class GlOnboarding extends LitElement { value=${this._state.walkthroughProgress.progress} > +
+
Open Walkthrough
+
+ ${this.renderWalkthroughProgress()} +
`; } + private renderWalkthroughProgress(): unknown { + if (this._state.walkthroughProgress == null) return undefined; + + return html`

+ Walkthrough Progress + (${this._state.walkthroughProgress.doneCount}/${this._state.walkthroughProgress.allCount}) +

+
    + ${Object.entries(walkthroughProgressSteps).map(([key, label]) => { + const isCompleted = + this._state.walkthroughProgress!.state[key as keyof typeof walkthroughProgressSteps]; + return html`
  • + + ${label} +
  • `; + })} +
`; + } + private onDismissWalkthrough = () => { this._state.walkthroughProgress = undefined; this._ipc.sendCommand(DismissWalkthroughSection); diff --git a/src/webviews/home/homeWebview.ts b/src/webviews/home/homeWebview.ts index d5480c2efc584..202f5ec57913d 100644 --- a/src/webviews/home/homeWebview.ts +++ b/src/webviews/home/homeWebview.ts @@ -18,6 +18,7 @@ import { supportedOrderedCloudIntegrationIds, } from '../../constants.integrations'; import type { HomeTelemetryContext, Source } from '../../constants.telemetry'; +import type { WalkthroughContextKeys } from '../../constants.walkthroughs'; import type { Container } from '../../container'; import { executeGitCommand } from '../../git/actions'; import { revealBranch } from '../../git/actions/branch'; @@ -175,7 +176,7 @@ export class HomeWebviewProvider implements WebviewProvider = {}; + for (const [key, value] of walkthroughState) { + state[key] = value; + } - void this.host.notify(DidChangeWalkthroughProgress, { + return { allCount: this.container.walkthrough.walkthroughSize, doneCount: this.container.walkthrough.doneCount, progress: this.container.walkthrough.progress, - }); + state: state as Record, + }; + } + + private notifyDidChangeProgress() { + const state = this.getWalkthroughProgress(); + if (state == null) return; + + void this.host.notify(DidChangeWalkthroughProgress, state); } private notifyDidChangeConfig() { diff --git a/src/webviews/home/protocol.ts b/src/webviews/home/protocol.ts index f63cd7f5b3a28..d783bacb92f36 100644 --- a/src/webviews/home/protocol.ts +++ b/src/webviews/home/protocol.ts @@ -1,5 +1,6 @@ import type { IntegrationDescriptor } from '../../constants.integrations'; import type { Source } from '../../constants.telemetry'; +import type { WalkthroughContextKeys } from '../../constants.walkthroughs'; import type { GitBranchMergedStatus } from '../../git/gitProvider'; import type { GitBranchStatus, GitTrackingState, GitTrackingUpstream } from '../../git/models/branch'; import type { GitDiffFileStats } from '../../git/models/diff'; @@ -43,6 +44,7 @@ export interface State extends WebviewState { doneCount: number; allCount: number; progress: number; + state: Record; }; previewEnabled: boolean; newInstall: boolean; @@ -300,6 +302,7 @@ export interface DidChangeProgressParams { progress: number; doneCount: number; allCount: number; + state: Record; } export const DidChangeWalkthroughProgress = new IpcNotification( scope,