diff --git a/src/plus/webviews/graph/graphWebview.ts b/src/plus/webviews/graph/graphWebview.ts index 710e1ea..2046eca 100644 --- a/src/plus/webviews/graph/graphWebview.ts +++ b/src/plus/webviews/graph/graphWebview.ts @@ -7,6 +7,7 @@ import { configuration } from '../../../configuration'; import { Commands, ContextKeys } from '../../../constants'; import type { Container } from '../../../container'; import { setContext } from '../../../context'; +import { PlusFeatures } from '../../../features'; import type { GitCommit } from '../../../git/models/commit'; import type { GitGraph } from '../../../git/models/graph'; import type { Repository, RepositoryChangeEvent } from '../../../git/models/repository'; @@ -22,12 +23,14 @@ import { RepositoryFolderNode } from '../../../views/nodes/viewNode'; import type { IpcMessage } from '../../../webviews/protocol'; import { onIpc } from '../../../webviews/protocol'; import { WebviewBase } from '../../../webviews/webviewBase'; +import type { SubscriptionChangeEvent } from '../../subscription/subscriptionService'; import { ensurePlusFeaturesEnabled } from '../../subscription/utils'; import type { GraphCompositeConfig, GraphRepository, State } from './protocol'; import { DidChangeCommitsNotificationType, DidChangeGraphConfigurationNotificationType, DidChangeNotificationType, + DidChangeSubscriptionNotificationType, DismissPreviewCommandType, GetMoreCommitsCommandType, UpdateColumnCommandType, @@ -69,6 +72,7 @@ export class GraphWebview extends WebviewBase { return this._selection; } + private _etagSubscription?: number; private _etagRepository?: number; private _repositoryEventsDisposable: Disposable | undefined; private _repositoryGraph?: GitGraph; @@ -88,12 +92,16 @@ export class GraphWebview extends WebviewBase { 'graphWebview', Commands.ShowGraphPage, ); - this.disposables.push(configuration.onDidChange(this.onConfigurationChanged, this), { - dispose: () => { - this._statusBarItem?.dispose(); - void this._repositoryEventsDisposable?.dispose(); + this.disposables.push( + configuration.onDidChange(this.onConfigurationChanged, this), + { + dispose: () => { + this._statusBarItem?.dispose(); + void this._repositoryEventsDisposable?.dispose(); + }, }, - }); + this.container.subscription.onDidChange(this.onSubscriptionChanged, this), + ); this.onConfigurationChanged(); } @@ -232,6 +240,13 @@ export class GraphWebview extends WebviewBase { this.updateState(); } + private onSubscriptionChanged(e: SubscriptionChangeEvent) { + if (e.etag === this._etagSubscription) return; + + this._etagSubscription = e.etag; + void this.notifyDidChangeSubscription(); + } + private onThemeChanged(theme: ColorTheme) { if (this._theme != null) { if ( @@ -341,6 +356,17 @@ export class GraphWebview extends WebviewBase { } @debug() + private async notifyDidChangeSubscription() { + if (!this.isReady || !this.visible) return false; + + const access = await this.container.git.access(PlusFeatures.Graph, this.repository?.path); + return this.notify(DidChangeSubscriptionNotificationType, { + subscription: access.subscription.current, + allowed: access.allowed, + }); + } + + @debug() private async notifyDidChangeCommits() { if (!this.isReady || !this.visible) return false; @@ -385,6 +411,13 @@ export class GraphWebview extends WebviewBase { // If we have a set of data refresh to the same set const limit = this._repositoryGraph?.paging?.limit ?? config.defaultItemLimit; + // only check on private + const access = await this.container.git.access(PlusFeatures.Graph, this.repository?.path); + // TODO: probably not the right place to set this + if (this._etagSubscription == null) { + this._etagSubscription = this.container.subscription.etag; + } + const data = await this.container.git.getCommitsForGraph( this.repository.path, this._panel!.webview.asWebviewUri, @@ -396,6 +429,9 @@ export class GraphWebview extends WebviewBase { previewBanner: this.previewBanner, repositories: formatRepositories(this.container.git.openRepositories), selectedRepository: this.repository.path, + selectedVisibility: access.visibility, + subscription: access.subscription.current, + allowed: access.allowed, rows: data.rows, paging: { startingCursor: data.paging?.startingCursor, diff --git a/src/plus/webviews/graph/protocol.ts b/src/plus/webviews/graph/protocol.ts index 9f6605b..9bfc76c 100644 --- a/src/plus/webviews/graph/protocol.ts +++ b/src/plus/webviews/graph/protocol.ts @@ -1,10 +1,15 @@ import type { CommitType, GraphRow, Remote } from '@gitkraken/gitkraken-components'; import type { GraphColumnConfig, GraphConfig } from '../../../config'; +import type { RepositoryVisibility } from '../../../git/gitProvider'; +import type { Subscription } from '../../../subscription'; import { IpcCommandType, IpcNotificationType } from '../../../webviews/protocol'; export interface State { repositories?: GraphRepository[]; selectedRepository?: string; + selectedVisibility?: RepositoryVisibility; + subscription?: Subscription; + allowed?: boolean; rows?: GraphRow[]; paging?: GraphPaging; config?: GraphCompositeConfig; @@ -94,6 +99,14 @@ export const DidChangeGraphConfigurationNotificationType = new IpcNotificationTy 'graph/configuration/didChange', ); +export interface DidChangeSubscriptionParams { + subscription: Subscription; + allowed: boolean; +} +export const DidChangeSubscriptionNotificationType = new IpcNotificationType( + 'graph/subscription/didChange', +); + export interface DidChangeCommitsParams { rows: GraphRow[]; paging?: GraphPaging; diff --git a/src/webviews/apps/plus/graph/GraphWrapper.tsx b/src/webviews/apps/plus/graph/GraphWrapper.tsx index c0d4419..80a4627 100644 --- a/src/webviews/apps/plus/graph/GraphWrapper.tsx +++ b/src/webviews/apps/plus/graph/GraphWrapper.tsx @@ -14,6 +14,9 @@ import type { GraphRepository, State, } from '../../../../plus/webviews/graph/protocol'; +import type { Subscription } from '../../../../subscription'; +import { SubscriptionState } from '../../../../subscription'; +import { fromNow } from '../../shared/date'; export interface GraphWrapperProps extends State { nonce?: string; @@ -114,6 +117,8 @@ export function GraphWrapper({ repositories = [], rows = [], selectedRepository, + subscription, + allowed, config, paging, onSelectRepository, @@ -139,7 +144,12 @@ export function GraphWrapper({ const [mainWidth, setMainWidth] = useState(); const [mainHeight, setMainHeight] = useState(); const mainRef = useRef(null); - const [showBanner, setShowBanner] = useState(previewBanner); + // banner + const [showPreview, setShowPreview] = useState(previewBanner); + // account + const [showAccount, setShowAccount] = useState(true); + const [isAllowed, setIsAllowed] = useState(allowed ?? false); + const [subscriptionSnapshot, setSubscriptionSnapshot] = useState(subscription); // repo selection UI const [repoExpanded, setRepoExpanded] = useState(false); @@ -173,6 +183,8 @@ export function GraphWrapper({ setPagingState(state.paging); setIsLoading(false); setStyleProps(getStyleProps(state.mixedColumnColors)); + setIsAllowed(state.allowed ?? false); + setSubscriptionSnapshot(state.subscription); } useEffect(() => { @@ -207,15 +219,135 @@ export function GraphWrapper({ onSelectionChange?.(graphRows.map(r => r.sha)); }; - const handleDismissBanner = () => { - setShowBanner(false); + const handleDismissPreview = () => { + setShowPreview(false); onDismissPreview?.(); }; + const handleDismissAccount = () => { + setShowAccount(false); + }; + + const renderAlertContent = () => { + if (subscriptionSnapshot == null) return; + + let icon = 'account'; + let modifier = ''; + let content; + let actions; + switch (subscriptionSnapshot.state) { + case SubscriptionState.Free: + case SubscriptionState.Paid: + return; + case SubscriptionState.FreeInPreview: + icon = 'calendar'; + modifier = 'neutral'; + content = ( + <> +

Trial Preview

+

+ You're able to view the Commit Graph with any repository until your preview expires + {subscriptionSnapshot.previewTrial + ? ` ${fromNow(new Date(subscriptionSnapshot.previewTrial.expiresOn))}` + : ''} + . +

+ + ); + break; + case SubscriptionState.FreePreviewExpired: + icon = 'warning'; + modifier = 'warning'; + content = ( + <> +

Extend Your Trial

+

Sign in to extend your free trial an additional 7-days.

+ + ); + actions = ( + <> + + Try for 7-days + {' '} + + View Plans + + + ); + break; + case SubscriptionState.FreePlusInTrial: + icon = 'calendar'; + modifier = 'neutral'; + content = ( + <> +

Extended Trial

+

+ You're able to view the Commit Graph with any repository until your trial expires + {subscriptionSnapshot.previewTrial + ? ` ${fromNow(new Date(subscriptionSnapshot.previewTrial.expiresOn))}` + : ''} + . +

+ + ); + break; + case SubscriptionState.FreePlusTrialExpired: + icon = 'warning'; + modifier = 'warning'; + content = ( + <> +

Trial Expired

+

+ Upgrade your account to use the Commit Graph and other GitLens+ features on private repos. +

+

+ + Upgrade Your Account + +

+ + ); + break; + case SubscriptionState.VerificationRequired: + icon = 'unverified'; + modifier = 'warning'; + content = ( + <> +

Please verify your email

+

Please verify the email for the account you created.

+ + ); + actions = ( + <> + + Resend Verification Email + + + Refresh Verification Status + + + ); + break; + } + + return ( +
+ +
{content}
+ {actions &&
{actions}
} + {isAllowed && ( + + )} +
+ ); + }; + return ( <> - {showBanner && ( -
+
+ {showPreview && (
@@ -230,13 +362,20 @@ export function GraphWrapper({ .

-
-
- )} -
+ )} + {showAccount && renderAlertContent()} +
+
+ {!isAllowed &&
} {currentRepository !== undefined ? ( <> {mainWidth !== undefined && mainHeight !== undefined && ( @@ -263,7 +402,7 @@ export function GraphWrapper({

No repository is selected

)}
-