5623 add an inviteteam onboarding step (#5769)

## Changes
- add a new invite Team onboarding step
- update currentUser.state to currentUser.onboardingStep

## Edge cases
We will never display invite team onboarding step 
- if number of workspaceMember > 1
- if a workspaceMember as been deleted

## Important changes
Update typeorm package version to 0.3.20 because we needed a fix on
`indexPredicates` pushed in 0.3.20 version
(https://github.com/typeorm/typeorm/issues/10191)

## Result
<img width="844" alt="image"
src="https://github.com/twentyhq/twenty/assets/29927851/0dab54cf-7c66-4c64-b0c9-b0973889a148">



https://github.com/twentyhq/twenty/assets/29927851/13268d0a-cfa7-42a4-84c6-9e1fbbe48912
This commit is contained in:
martmull
2024-06-12 21:13:18 +02:00
committed by GitHub
parent 2fdd2f4949
commit 3986824017
60 changed files with 1009 additions and 372 deletions

View File

@ -0,0 +1,9 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class OnboardingStepSuccess {
@Field(() => Boolean, {
description: 'Boolean that confirms query was dispatched',
})
success: boolean;
}

View File

@ -0,0 +1,4 @@
export enum OnboardingStep {
SYNC_EMAIL = 'SYNC_EMAIL',
INVITE_TEAM = 'INVITE_TEAM',
}

View File

@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
import { OnboardingResolver } from 'src/engine/core-modules/onboarding/onboarding.resolver';
import { KeyValuePairModule } from 'src/engine/core-modules/key-value-pair/key-value-pair.module';
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
@Module({
imports: [DataSourceModule, UserWorkspaceModule, KeyValuePairModule],
exports: [OnboardingService],
providers: [OnboardingService, OnboardingResolver],
})
export class OnboardingModule {}

View File

@ -0,0 +1,29 @@
import { UseGuards } from '@nestjs/common';
import { Mutation, Resolver } from '@nestjs/graphql';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { OnboardingStepSuccess } from 'src/engine/core-modules/onboarding/dtos/onboarding-step-success.dto';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { User } from 'src/engine/core-modules/user/user.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { OnboardingService } from 'src/engine/core-modules/onboarding/onboarding.service';
@UseGuards(JwtAuthGuard)
@Resolver()
export class OnboardingResolver {
constructor(private readonly onboardingService: OnboardingService) {}
@Mutation(() => OnboardingStepSuccess)
async skipSyncEmailOnboardingStep(
@AuthUser() user: User,
@AuthWorkspace() workspace: Workspace,
): Promise<OnboardingStepSuccess> {
await this.onboardingService.skipSyncEmailOnboardingStep(
user.id,
workspace.id,
);
return { success: true };
}
}

View File

@ -0,0 +1,98 @@
import { Injectable } from '@nestjs/common';
import { KeyValuePairService } from 'src/engine/core-modules/key-value-pair/key-value-pair.service';
import { OnboardingStep } from 'src/engine/core-modules/onboarding/enums/onboarding-step.enum';
import { User } from 'src/engine/core-modules/user/user.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
enum OnboardingStepValues {
SKIPPED = 'SKIPPED',
}
enum OnboardingStepKeys {
SYNC_EMAIL_ONBOARDING_STEP = 'SYNC_EMAIL_ONBOARDING_STEP',
INVITE_TEAM_ONBOARDING_STEP = 'INVITE_TEAM_ONBOARDING_STEP',
}
type OnboardingKeyValueType = {
[OnboardingStepKeys.SYNC_EMAIL_ONBOARDING_STEP]: OnboardingStepValues;
[OnboardingStepKeys.INVITE_TEAM_ONBOARDING_STEP]: OnboardingStepValues;
};
@Injectable()
export class OnboardingService {
constructor(
private readonly userWorkspaceService: UserWorkspaceService,
private readonly keyValuePairService: KeyValuePairService<OnboardingKeyValueType>,
@InjectObjectMetadataRepository(ConnectedAccountWorkspaceEntity)
private readonly connectedAccountRepository: ConnectedAccountRepository,
) {}
private async isSyncEmailOnboardingStep(user: User, workspace: Workspace) {
const syncEmailValue = await this.keyValuePairService.get({
userId: user.id,
workspaceId: workspace.id,
key: OnboardingStepKeys.SYNC_EMAIL_ONBOARDING_STEP,
});
const isSyncEmailSkipped = syncEmailValue === OnboardingStepValues.SKIPPED;
const connectedAccounts =
await this.connectedAccountRepository.getAllByUserId(
user.id,
workspace.id,
);
return !isSyncEmailSkipped && !connectedAccounts?.length;
}
private async isInviteTeamOnboardingStep(workspace: Workspace) {
const inviteTeamValue = await this.keyValuePairService.get({
workspaceId: workspace.id,
key: OnboardingStepKeys.INVITE_TEAM_ONBOARDING_STEP,
});
const isInviteTeamSkipped =
inviteTeamValue === OnboardingStepValues.SKIPPED;
const workspaceMemberCount =
await this.userWorkspaceService.getWorkspaceMemberCount(workspace.id);
return (
!isInviteTeamSkipped &&
(!workspaceMemberCount || workspaceMemberCount <= 1)
);
}
async getOnboardingStep(
user: User,
workspace: Workspace,
): Promise<OnboardingStep | null> {
if (await this.isSyncEmailOnboardingStep(user, workspace)) {
return OnboardingStep.SYNC_EMAIL;
}
if (await this.isInviteTeamOnboardingStep(workspace)) {
return OnboardingStep.INVITE_TEAM;
}
return null;
}
async skipInviteTeamOnboardingStep(workspaceId: string) {
await this.keyValuePairService.set({
workspaceId,
key: OnboardingStepKeys.INVITE_TEAM_ONBOARDING_STEP,
value: OnboardingStepValues.SKIPPED,
});
}
async skipSyncEmailOnboardingStep(userId: string, workspaceId: string) {
await this.keyValuePairService.set({
userId,
workspaceId,
key: OnboardingStepKeys.SYNC_EMAIL_ONBOARDING_STEP,
value: OnboardingStepValues.SKIPPED,
});
}
}