Refactor onboarding user vars to be absent when user is fully onboarded (#6531)

In this PR:
- take feedbacks from: https://github.com/twentyhq/twenty/pull/6530 /
https://github.com/twentyhq/twenty/pull/6529 /
https://github.com/twentyhq/twenty/pull/6526 /
https://github.com/twentyhq/twenty/pull/6512
- refactor onboarding uservars to be absent when the user is fully
onboarded: isStepComplete ==> isStepIncomplete
- introduce a new workspace.activationStatus: CREATION_ONGOING

I'm retesting the whole flow:
- with/without BILLING
- sign in with/without SSO
- sign up with/without SSO
- another workspaceMembers join the team
- subscriptionCanceled
- access to billingPortal
This commit is contained in:
Charles Bochet
2024-08-04 20:37:36 +02:00
committed by GitHub
parent c543716381
commit 03204021cb
49 changed files with 517 additions and 364 deletions

View File

@ -19,10 +19,10 @@ export class OnboardingResolver {
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
): Promise<OnboardingStepSuccess> {
await this.onboardingService.toggleOnboardingConnectAccountCompletion({
await this.onboardingService.setOnboardingConnectAccountPending({
userId: user.id,
workspaceId: workspace.id,
value: true,
value: false,
});
return { success: true };

View File

@ -1,66 +1,40 @@
import { Injectable } from '@nestjs/common';
import { SubscriptionStatus } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { IsFeatureEnabledService } from 'src/engine/core-modules/feature-flag/services/is-feature-enabled.service';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service';
import { User } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceActivationStatus } from 'src/engine/core-modules/workspace/workspace.entity';
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
import { isDefined } from 'src/utils/is-defined';
export enum OnboardingStepKeys {
ONBOARDING_CONNECT_ACCOUNT_COMPLETE = 'ONBOARDING_CONNECT_ACCOUNT_COMPLETE',
ONBOARDING_INVITE_TEAM_COMPLETE = 'ONBOARDING_INVITE_TEAM_COMPLETE',
ONBOARDING_CREATE_PROFILE_COMPLETE = 'ONBOARDING_CREATE_PROFILE_COMPLETE',
ONBOARDING_CONNECT_ACCOUNT_PENDING = 'ONBOARDING_CONNECT_ACCOUNT_PENDING',
ONBOARDING_INVITE_TEAM_PENDING = 'ONBOARDING_INVITE_TEAM_PENDING',
ONBOARDING_CREATE_PROFILE_PENDING = 'ONBOARDING_CREATE_PROFILE_PENDING',
}
export type OnboardingKeyValueTypeMap = {
[OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_COMPLETE]: boolean;
[OnboardingStepKeys.ONBOARDING_INVITE_TEAM_COMPLETE]: boolean;
[OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_COMPLETE]: boolean;
[OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING]: boolean;
[OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING]: boolean;
[OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING]: boolean;
};
@Injectable()
export class OnboardingService {
constructor(
private readonly billingSubscriptionService: BillingSubscriptionService,
private readonly environmentService: EnvironmentService,
private readonly isFeatureEnabledService: IsFeatureEnabledService,
private readonly billingService: BillingService,
private readonly userVarsService: UserVarsService<OnboardingKeyValueTypeMap>,
) {}
private async isSubscriptionIncompleteOnboardingStatus(user: User) {
const isBillingEnabled = this.environmentService.get('IS_BILLING_ENABLED');
if (!isBillingEnabled) {
return false;
}
const isFreeAccessEnabled =
await this.isFeatureEnabledService.isFeatureEnabled(
FeatureFlagKey.IsFreeAccessEnabled,
const hasSubscription =
await this.billingService.hasWorkspaceActiveSubscriptionOrFreeAccess(
user.defaultWorkspaceId,
);
if (isFreeAccessEnabled) {
return false;
}
const currentBillingSubscription =
await this.billingSubscriptionService.getCurrentBillingSubscription({
workspaceId: user.defaultWorkspaceId,
});
return (
!isDefined(currentBillingSubscription) ||
currentBillingSubscription?.status === SubscriptionStatus.Incomplete
);
return !hasSubscription;
}
private async isWorkspaceActivationOnboardingStatus(user: User) {
private isWorkspaceActivationPending(user: User) {
return (
user.defaultWorkspace.activationStatus ===
WorkspaceActivationStatus.PENDING_CREATION
@ -72,7 +46,7 @@ export class OnboardingService {
return OnboardingStatus.PLAN_REQUIRED;
}
if (await this.isWorkspaceActivationOnboardingStatus(user)) {
if (this.isWorkspaceActivationPending(user)) {
return OnboardingStatus.WORKSPACE_ACTIVATION;
}
@ -81,33 +55,33 @@ export class OnboardingService {
workspaceId: user.defaultWorkspaceId,
});
const isProfileCreationComplete =
userVars.get(OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_COMPLETE) ===
const isProfileCreationPending =
userVars.get(OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING) ===
true;
const isConnectAccountComplete =
userVars.get(OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_COMPLETE) ===
const isConnectAccountPending =
userVars.get(OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING) ===
true;
const isInviteTeamComplete =
userVars.get(OnboardingStepKeys.ONBOARDING_INVITE_TEAM_COMPLETE) === true;
const isInviteTeamPending =
userVars.get(OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING) === true;
if (!isProfileCreationComplete) {
if (isProfileCreationPending) {
return OnboardingStatus.PROFILE_CREATION;
}
if (!isConnectAccountComplete) {
if (isConnectAccountPending) {
return OnboardingStatus.SYNC_EMAIL;
}
if (!isInviteTeamComplete) {
if (isInviteTeamPending) {
return OnboardingStatus.INVITE_TEAM;
}
return OnboardingStatus.COMPLETED;
}
async toggleOnboardingConnectAccountCompletion({
async setOnboardingConnectAccountPending({
userId,
workspaceId,
value,
@ -116,29 +90,48 @@ export class OnboardingService {
workspaceId: string;
value: boolean;
}) {
if (!value) {
await this.userVarsService.delete({
userId,
workspaceId,
key: OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING,
});
return;
}
await this.userVarsService.set({
userId,
workspaceId: workspaceId,
key: OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_COMPLETE,
value,
key: OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING,
value: true,
});
}
async toggleOnboardingInviteTeamCompletion({
async setOnboardingInviteTeamPending({
workspaceId,
value,
}: {
workspaceId: string;
value: boolean;
}) {
if (!value) {
await this.userVarsService.delete({
workspaceId,
key: OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING,
});
return;
}
await this.userVarsService.set({
workspaceId,
key: OnboardingStepKeys.ONBOARDING_INVITE_TEAM_COMPLETE,
value,
key: OnboardingStepKeys.ONBOARDING_INVITE_TEAM_PENDING,
value: true,
});
}
async toggleOnboardingCreateProfileCompletion({
async setOnboardingCreateProfileCompletion({
userId,
workspaceId,
value,
@ -147,11 +140,21 @@ export class OnboardingService {
workspaceId: string;
value: boolean;
}) {
if (!value) {
await this.userVarsService.delete({
userId,
workspaceId,
key: OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING,
});
return;
}
await this.userVarsService.set({
userId,
workspaceId,
key: OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_COMPLETE,
value,
key: OnboardingStepKeys.ONBOARDING_CREATE_PROFILE_PENDING,
value: true,
});
}
}