From 70e4d8d36e38a269a7641469dcd742bb44fe21cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Mon, 20 Jul 2026 11:25:52 +0200 Subject: [PATCH] Skip install-apps onboarding step for invited users (#22823) ## What The onboarding "install apps" step was proposed to every new user, including those joining an existing workspace through an invitation link. Now it is only proposed to the user who creates the workspace. ## Why App installation only makes sense for the workspace creator. Invited members should go straight to profile creation. ## How `activateOnboardingForUser` set the `ONBOARDING_INSTALL_APPS_PENDING` flag unconditionally, and that flag is the sole driver of the `APPS_INSTALLATION` status (the frontend just follows the backend status). Gated the setter behind a new `shouldShowInstallAppsStep` flag, mirroring the existing `shouldShowConnectAccountStep` flag: creator path passes `true`, join path (personal invitation, public invite link, SSO into an existing workspace) passes `false`. Backend-only change, no migration needed. Covered by unit tests for both branches. Review in cubic --- .../auth/services/sign-in-up.service.spec.ts | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts index 8f7fa9dccf..7dc7b9051b 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.spec.ts @@ -32,7 +32,7 @@ const createSignInUpServiceForTests = () => { const mockWorkspaceRepository = { count: jest.fn(), - create: jest.fn(), + create: jest.fn((workspace) => workspace), }; const mockConfigurationValues: MockConfigurationValues = { @@ -50,7 +50,8 @@ const createSignInUpServiceForTests = () => { const queryRunnerMock = { manager: { - save: jest.fn(), + save: jest.fn((_entity, entity) => entity), + update: jest.fn(), }, connect: jest.fn(), startTransaction: jest.fn(), @@ -101,7 +102,9 @@ const createSignInUpServiceForTests = () => { invalidateAndRecompute: jest.fn(), } as any, { - createWorkspaceCustomApplication: jest.fn(), + createWorkspaceCustomApplication: jest.fn().mockResolvedValue({ + universalIdentifier: 'custom-application-universal-identifier', + }), } as any, { uploadWorkspaceLogoFromUrl: jest.fn(), @@ -122,6 +125,9 @@ const createSignInUpServiceForTests = () => { } as any, { createQueryRunner: jest.fn(() => queryRunnerMock), + transaction: jest.fn(async (runInTransaction) => + runInTransaction({ queryRunner: queryRunnerMock }), + ), } as any, ); @@ -349,4 +355,35 @@ describe('SignInUpService onboarding steps', () => { mockOnboardingService.setOnboardingConnectAccountPending, ).not.toHaveBeenCalled(); }); + + it('flags the install-apps step for a user creating a new workspace', async () => { + const { + service, + mockOnboardingService, + mockWorkspaceRepository, + mockUserRepository, + } = createSignInUpServiceForTests(); + + mockWorkspaceRepository.count.mockResolvedValue(0); + mockUserRepository.count.mockResolvedValue(0); + + await service.signUpOnNewWorkspace( + { + type: 'newUserWithPicture', + newUserWithPicture: { + email: 'creator@gmail.com', + firstName: 'Creator', + lastName: 'User', + }, + }, + { displayName: 'Acme Inc' }, + ); + + expect( + mockOnboardingService.setOnboardingInstallAppsPending, + ).toHaveBeenCalledWith( + expect.objectContaining({ value: true }), + expect.anything(), + ); + }); });