From 0043665202c4eb36b43355c42d1f02b07f09b09a Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Tue, 17 Jun 2025 10:26:22 +0200 Subject: [PATCH] BREAKING CHANGE: Fix broken support button (#12648) ## Context Support button was missing for configuration having support enabled (FrontApp) image ## How Recently, we changed some enums from lowercase to uppercase in graphql ## Problem resolution supportDriver was typed as a string where we could have used SupportDriver type. I'm exposing it in the graphql generated files to re-use in the front so this issue cannot happen anymore --- .../src/generated-metadata/graphql.ts | 75 +++++++++++-------- .../twenty-front/src/generated/graphql.tsx | 12 ++- .../auth/hooks/__tests__/useAuth.test.tsx | 3 +- .../client-config/states/supportChatState.ts | 4 +- .../__stories__/SupportDropdown.stories.tsx | 6 +- .../modules/support/hooks/useSupportChat.ts | 4 +- .../src/testing/mock-data/config.ts | 8 +- .../client-config.controller.spec.ts | 4 +- .../client-config/client-config.entity.ts | 6 +- .../interfaces/support.interface.ts | 6 ++ 10 files changed, 82 insertions(+), 46 deletions(-) diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index fbf7ffdaf..ef07a1e90 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -134,21 +134,28 @@ export type AuthorizeApp = { redirectUrl: Scalars['String']['output']; }; -export type AvailableWorkspaceOutput = { - __typename?: 'AvailableWorkspaceOutput'; +export type AvailableWorkspace = { + __typename?: 'AvailableWorkspace'; displayName?: Maybe; id: Scalars['String']['output']; + inviteHash?: Maybe; + loginToken?: Maybe; logo?: Maybe; + personalInviteToken?: Maybe; sso: Array; workspaceUrls: WorkspaceUrls; }; -export type AvailableWorkspacesToJoin = { - __typename?: 'AvailableWorkspacesToJoin'; - displayName?: Maybe; - id: Scalars['String']['output']; - logo?: Maybe; - workspaceUrl: Scalars['String']['output']; +export type AvailableWorkspaces = { + __typename?: 'AvailableWorkspaces'; + availableWorkspacesForSignIn: Array; + availableWorkspacesForSignUp: Array; +}; + +export type AvailableWorkspacesAndAccessTokensOutput = { + __typename?: 'AvailableWorkspacesAndAccessTokensOutput'; + availableWorkspaces: AvailableWorkspaces; + tokens: AuthTokenPair; }; export type Billing = { @@ -308,7 +315,7 @@ export enum CaptchaDriverType { export type CheckUserExistOutput = { __typename?: 'CheckUserExistOutput'; - availableWorkspaces: Array; + availableWorkspacesCount: Scalars['Float']['output']; exists: Scalars['Boolean']['output']; isEmailVerified: Scalars['Boolean']['output']; }; @@ -957,7 +964,6 @@ export type Mutation = { createOneRole: Role; createOneServerlessFunction: ServerlessFunction; createSAMLIdentityProvider: SetupSsoOutput; - createUserAndWorkspace: SignUpOutput; createWorkflowVersionStep: WorkflowAction; deactivateWorkflowVersion: Scalars['Boolean']['output']; deleteApprovedAccessDomain: Scalars['Boolean']['output']; @@ -991,8 +997,10 @@ export type Mutation = { resendWorkspaceInvitation: SendInvitationsOutput; runWorkflowVersion: WorkflowRun; sendInvitations: SendInvitationsOutput; - signUp: SignUpOutput; + signIn: AvailableWorkspacesAndAccessTokensOutput; + signUp: AvailableWorkspacesAndAccessTokensOutput; signUpInNewWorkspace: SignUpOutput; + signUpInWorkspace: SignUpOutput; skipSyncEmailOnboardingStep: OnboardingStepSuccess; submitFormStep: Scalars['Boolean']['output']; switchToEnterprisePlan: BillingUpdateOutput; @@ -1119,16 +1127,6 @@ export type MutationCreateSamlIdentityProviderArgs = { }; -export type MutationCreateUserAndWorkspaceArgs = { - captchaToken?: InputMaybe; - email: Scalars['String']['input']; - firstName?: InputMaybe; - lastName?: InputMaybe; - locale?: InputMaybe; - picture?: InputMaybe; -}; - - export type MutationCreateWorkflowVersionStepArgs = { input: CreateWorkflowVersionStepInput; }; @@ -1275,7 +1273,21 @@ export type MutationSendInvitationsArgs = { }; +export type MutationSignInArgs = { + captchaToken?: InputMaybe; + email: Scalars['String']['input']; + password: Scalars['String']['input']; +}; + + export type MutationSignUpArgs = { + captchaToken?: InputMaybe; + email: Scalars['String']['input']; + password: Scalars['String']['input']; +}; + + +export type MutationSignUpInWorkspaceArgs = { captchaToken?: InputMaybe; email: Scalars['String']['input']; locale?: InputMaybe; @@ -1666,7 +1678,6 @@ export type Query = { getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; index: Index; indexMetadatas: IndexConnection; - listAvailableWorkspaces: Array; object: Object; objects: ObjectConnection; plans: Array; @@ -1744,7 +1755,7 @@ export type QueryGetIndicatorHealthStatusArgs = { export type QueryGetPublicWorkspaceDataByDomainArgs = { - origin: Scalars['String']['input']; + origin?: InputMaybe; }; @@ -1798,12 +1809,6 @@ export type QueryIndexMetadatasArgs = { }; -export type QueryListAvailableWorkspacesArgs = { - captchaToken?: InputMaybe; - email: Scalars['String']['input']; -}; - - export type QueryObjectArgs = { id: Scalars['UUID']['input']; }; @@ -2064,6 +2069,7 @@ export enum SettingPermissionType { DATA_MODEL = 'DATA_MODEL', ROLES = 'ROLES', SECURITY = 'SECURITY', + WORKFLOWS = 'WORKFLOWS', WORKSPACE = 'WORKSPACE', WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' } @@ -2152,10 +2158,15 @@ export enum SubscriptionStatus { export type Support = { __typename?: 'Support'; - supportDriver: Scalars['String']['output']; + supportDriver: SupportDriver; supportFrontChatId?: Maybe; }; +export enum SupportDriver { + FRONT = 'FRONT', + NONE = 'NONE' +} + export type SystemHealth = { __typename?: 'SystemHealth'; services: Array; @@ -2386,7 +2397,7 @@ export type UpsertSettingPermissionsInput = { export type User = { __typename?: 'User'; - availableWorkspaces: Array; + availableWorkspaces: AvailableWorkspaces; canAccessFullAdminPanel: Scalars['Boolean']['output']; canImpersonate: Scalars['Boolean']['output']; createdAt: Scalars['DateTime']['output']; @@ -2406,7 +2417,7 @@ export type User = { passwordHash?: Maybe; supportUserHash?: Maybe; updatedAt: Scalars['DateTime']['output']; - userVars: Scalars['JSONObject']['output']; + userVars?: Maybe; workspaceMember?: Maybe; workspaceMembers?: Maybe>; workspaces: Array; diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 1b9e2bfdc..b8964bbf8 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -1629,7 +1629,7 @@ export type QueryGetIndicatorHealthStatusArgs = { export type QueryGetPublicWorkspaceDataByDomainArgs = { - origin: Scalars['String']; + origin?: InputMaybe; }; @@ -1907,6 +1907,7 @@ export enum SettingPermissionType { DATA_MODEL = 'DATA_MODEL', ROLES = 'ROLES', SECURITY = 'SECURITY', + WORKFLOWS = 'WORKFLOWS', WORKSPACE = 'WORKSPACE', WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' } @@ -1995,10 +1996,15 @@ export enum SubscriptionStatus { export type Support = { __typename?: 'Support'; - supportDriver: Scalars['String']; + supportDriver: SupportDriver; supportFrontChatId?: Maybe; }; +export enum SupportDriver { + FRONT = 'FRONT', + NONE = 'NONE' +} + export type SystemHealth = { __typename?: 'SystemHealth'; services: Array; @@ -2741,7 +2747,7 @@ export type GetMeteredProductsUsageQuery = { __typename?: 'Query', getMeteredPro export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; -export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, isConfigVariablesInDbEnabled: boolean, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } }; +export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, isConfigVariablesInDbEnabled: boolean, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: SupportDriver, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } }; export type SearchQueryVariables = Exact<{ searchInput: Scalars['String']; diff --git a/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx b/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx index ee6b641cd..4d05f8364 100644 --- a/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx +++ b/packages/twenty-front/src/modules/auth/hooks/__tests__/useAuth.test.tsx @@ -15,6 +15,7 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWork import { renderHook } from '@testing-library/react'; import { iconsState } from 'twenty-ui/display'; import { email, mocks, password, results, token } from '../__mocks__/useAuth'; +import { SupportDriver } from '~/generated/graphql'; const redirectSpy = jest.fn(); @@ -160,7 +161,7 @@ describe('useAuth', () => { expect(state.billing).toBeNull(); expect(state.isDeveloperDefaultSignInPrefilled).toBe(false); expect(state.supportChat).toEqual({ - supportDriver: 'none', + supportDriver: SupportDriver.NONE, supportFrontChatId: null, }); }); diff --git a/packages/twenty-front/src/modules/client-config/states/supportChatState.ts b/packages/twenty-front/src/modules/client-config/states/supportChatState.ts index a49a4720b..b246dee59 100644 --- a/packages/twenty-front/src/modules/client-config/states/supportChatState.ts +++ b/packages/twenty-front/src/modules/client-config/states/supportChatState.ts @@ -1,10 +1,10 @@ -import { Support } from '~/generated/graphql'; import { createState } from 'twenty-ui/utilities'; +import { Support, SupportDriver } from '~/generated/graphql'; export const supportChatState = createState({ key: 'supportChatState', defaultValue: { - supportDriver: 'none', + supportDriver: SupportDriver.NONE, supportFrontChatId: null, }, }); diff --git a/packages/twenty-front/src/modules/support/components/__stories__/SupportDropdown.stories.tsx b/packages/twenty-front/src/modules/support/components/__stories__/SupportDropdown.stories.tsx index b1a06718d..1ab5e470e 100644 --- a/packages/twenty-front/src/modules/support/components/__stories__/SupportDropdown.stories.tsx +++ b/packages/twenty-front/src/modules/support/components/__stories__/SupportDropdown.stories.tsx @@ -15,6 +15,7 @@ import { } from '~/testing/mock-data/users'; import { SupportDropdown } from '@/support/components/SupportDropdown'; +import { SupportDriver } from '~/generated-metadata/graphql'; import { PrefetchLoadedDecorator } from '~/testing/decorators/PrefetchLoadedDecorator'; const meta: Meta = { @@ -32,7 +33,10 @@ const meta: Meta = { setCurrentWorkspace(mockCurrentWorkspace); setCurrentWorkspaceMember(mockedWorkspaceMemberData); setCurrentUser(mockedUserData); - setSupportChat({ supportDriver: 'front', supportFrontChatId: '1234' }); + setSupportChat({ + supportDriver: SupportDriver.FRONT, + supportFrontChatId: '1234', + }); return ; }, diff --git a/packages/twenty-front/src/modules/support/hooks/useSupportChat.ts b/packages/twenty-front/src/modules/support/hooks/useSupportChat.ts index 3abf75ab0..57b0e4d3a 100644 --- a/packages/twenty-front/src/modules/support/hooks/useSupportChat.ts +++ b/packages/twenty-front/src/modules/support/hooks/useSupportChat.ts @@ -5,8 +5,8 @@ import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading'; import { isNonEmptyString } from '@sniptt/guards'; import { useCallback, useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; -import { User, WorkspaceMember } from '~/generated-metadata/graphql'; import { isDefined } from 'twenty-shared/utils'; +import { User, WorkspaceMember } from '~/generated-metadata/graphql'; const insertScript = ({ src, @@ -74,7 +74,7 @@ export const useSupportChat = () => { useEffect(() => { if ( - supportChat?.supportDriver === 'front' && + supportChat?.supportDriver === 'FRONT' && isNonEmptyString(supportChat.supportFrontChatId) && isNonEmptyString(currentUser?.email) && isDefined(currentWorkspaceMember) && diff --git a/packages/twenty-front/src/testing/mock-data/config.ts b/packages/twenty-front/src/testing/mock-data/config.ts index f713d9e41..5ffccbd41 100644 --- a/packages/twenty-front/src/testing/mock-data/config.ts +++ b/packages/twenty-front/src/testing/mock-data/config.ts @@ -1,4 +1,8 @@ -import { CaptchaDriverType, ClientConfig } from '~/generated/graphql'; +import { + CaptchaDriverType, + ClientConfig, + SupportDriver, +} from '~/generated/graphql'; export const mockedClientConfig: ClientConfig = { signInPrefilled: true, @@ -17,7 +21,7 @@ export const mockedClientConfig: ClientConfig = { debugMode: false, analyticsEnabled: true, support: { - supportDriver: 'front', + supportDriver: SupportDriver.FRONT, supportFrontChatId: null, }, sentry: { diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.controller.spec.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.controller.spec.ts index 4686adf32..05723cc7c 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.controller.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.controller.spec.ts @@ -1,5 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { SupportDriver } from 'src/engine/core-modules/twenty-config/interfaces/support.interface'; + import { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service'; import { ClientConfigController } from './client-config.controller'; @@ -56,7 +58,7 @@ describe('ClientConfigController', () => { frontDomain: 'localhost', debugMode: true, support: { - supportDriver: 'none', + supportDriver: SupportDriver.NONE, supportFrontChatId: undefined, }, sentry: { diff --git a/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts b/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts index 756986dfd..3ea904dc3 100644 --- a/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts +++ b/packages/twenty-server/src/engine/core-modules/client-config/client-config.entity.ts @@ -1,5 +1,7 @@ import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; +import { SupportDriver } from 'src/engine/core-modules/twenty-config/interfaces/support.interface'; + import { BillingTrialPeriodDTO } from 'src/engine/core-modules/billing/dtos/billing-trial-period.dto'; import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; @@ -23,8 +25,8 @@ class Billing { @ObjectType() class Support { - @Field(() => String) - supportDriver: string; + @Field(() => SupportDriver) + supportDriver: SupportDriver; @Field(() => String, { nullable: true }) supportFrontChatId?: string; diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/interfaces/support.interface.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/interfaces/support.interface.ts index 2b2804c30..b431e731f 100644 --- a/packages/twenty-server/src/engine/core-modules/twenty-config/interfaces/support.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/twenty-config/interfaces/support.interface.ts @@ -1,4 +1,10 @@ +import { registerEnumType } from '@nestjs/graphql'; + export enum SupportDriver { NONE = 'NONE', FRONT = 'FRONT', } + +registerEnumType(SupportDriver, { + name: 'SupportDriver', +});