BREAKING CHANGE: Fix broken support button (#12648)

## Context 

Support button was missing for configuration having support enabled
(FrontApp)

<img width="1253" alt="image"
src="https://github.com/user-attachments/assets/930e3e0c-05a1-4a5b-820b-bb257f19fdde"
/>


## 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
This commit is contained in:
Charles Bochet
2025-06-17 10:26:22 +02:00
committed by GitHub
parent ccd16fb27f
commit 0043665202
10 changed files with 82 additions and 46 deletions

View File

@ -134,21 +134,28 @@ export type AuthorizeApp = {
redirectUrl: Scalars['String']['output']; redirectUrl: Scalars['String']['output'];
}; };
export type AvailableWorkspaceOutput = { export type AvailableWorkspace = {
__typename?: 'AvailableWorkspaceOutput'; __typename?: 'AvailableWorkspace';
displayName?: Maybe<Scalars['String']['output']>; displayName?: Maybe<Scalars['String']['output']>;
id: Scalars['String']['output']; id: Scalars['String']['output'];
inviteHash?: Maybe<Scalars['String']['output']>;
loginToken?: Maybe<Scalars['String']['output']>;
logo?: Maybe<Scalars['String']['output']>; logo?: Maybe<Scalars['String']['output']>;
personalInviteToken?: Maybe<Scalars['String']['output']>;
sso: Array<SsoConnection>; sso: Array<SsoConnection>;
workspaceUrls: WorkspaceUrls; workspaceUrls: WorkspaceUrls;
}; };
export type AvailableWorkspacesToJoin = { export type AvailableWorkspaces = {
__typename?: 'AvailableWorkspacesToJoin'; __typename?: 'AvailableWorkspaces';
displayName?: Maybe<Scalars['String']['output']>; availableWorkspacesForSignIn: Array<AvailableWorkspace>;
id: Scalars['String']['output']; availableWorkspacesForSignUp: Array<AvailableWorkspace>;
logo?: Maybe<Scalars['String']['output']>; };
workspaceUrl: Scalars['String']['output'];
export type AvailableWorkspacesAndAccessTokensOutput = {
__typename?: 'AvailableWorkspacesAndAccessTokensOutput';
availableWorkspaces: AvailableWorkspaces;
tokens: AuthTokenPair;
}; };
export type Billing = { export type Billing = {
@ -308,7 +315,7 @@ export enum CaptchaDriverType {
export type CheckUserExistOutput = { export type CheckUserExistOutput = {
__typename?: 'CheckUserExistOutput'; __typename?: 'CheckUserExistOutput';
availableWorkspaces: Array<AvailableWorkspaceOutput>; availableWorkspacesCount: Scalars['Float']['output'];
exists: Scalars['Boolean']['output']; exists: Scalars['Boolean']['output'];
isEmailVerified: Scalars['Boolean']['output']; isEmailVerified: Scalars['Boolean']['output'];
}; };
@ -957,7 +964,6 @@ export type Mutation = {
createOneRole: Role; createOneRole: Role;
createOneServerlessFunction: ServerlessFunction; createOneServerlessFunction: ServerlessFunction;
createSAMLIdentityProvider: SetupSsoOutput; createSAMLIdentityProvider: SetupSsoOutput;
createUserAndWorkspace: SignUpOutput;
createWorkflowVersionStep: WorkflowAction; createWorkflowVersionStep: WorkflowAction;
deactivateWorkflowVersion: Scalars['Boolean']['output']; deactivateWorkflowVersion: Scalars['Boolean']['output'];
deleteApprovedAccessDomain: Scalars['Boolean']['output']; deleteApprovedAccessDomain: Scalars['Boolean']['output'];
@ -991,8 +997,10 @@ export type Mutation = {
resendWorkspaceInvitation: SendInvitationsOutput; resendWorkspaceInvitation: SendInvitationsOutput;
runWorkflowVersion: WorkflowRun; runWorkflowVersion: WorkflowRun;
sendInvitations: SendInvitationsOutput; sendInvitations: SendInvitationsOutput;
signUp: SignUpOutput; signIn: AvailableWorkspacesAndAccessTokensOutput;
signUp: AvailableWorkspacesAndAccessTokensOutput;
signUpInNewWorkspace: SignUpOutput; signUpInNewWorkspace: SignUpOutput;
signUpInWorkspace: SignUpOutput;
skipSyncEmailOnboardingStep: OnboardingStepSuccess; skipSyncEmailOnboardingStep: OnboardingStepSuccess;
submitFormStep: Scalars['Boolean']['output']; submitFormStep: Scalars['Boolean']['output'];
switchToEnterprisePlan: BillingUpdateOutput; switchToEnterprisePlan: BillingUpdateOutput;
@ -1119,16 +1127,6 @@ export type MutationCreateSamlIdentityProviderArgs = {
}; };
export type MutationCreateUserAndWorkspaceArgs = {
captchaToken?: InputMaybe<Scalars['String']['input']>;
email: Scalars['String']['input'];
firstName?: InputMaybe<Scalars['String']['input']>;
lastName?: InputMaybe<Scalars['String']['input']>;
locale?: InputMaybe<Scalars['String']['input']>;
picture?: InputMaybe<Scalars['String']['input']>;
};
export type MutationCreateWorkflowVersionStepArgs = { export type MutationCreateWorkflowVersionStepArgs = {
input: CreateWorkflowVersionStepInput; input: CreateWorkflowVersionStepInput;
}; };
@ -1275,7 +1273,21 @@ export type MutationSendInvitationsArgs = {
}; };
export type MutationSignInArgs = {
captchaToken?: InputMaybe<Scalars['String']['input']>;
email: Scalars['String']['input'];
password: Scalars['String']['input'];
};
export type MutationSignUpArgs = { export type MutationSignUpArgs = {
captchaToken?: InputMaybe<Scalars['String']['input']>;
email: Scalars['String']['input'];
password: Scalars['String']['input'];
};
export type MutationSignUpInWorkspaceArgs = {
captchaToken?: InputMaybe<Scalars['String']['input']>; captchaToken?: InputMaybe<Scalars['String']['input']>;
email: Scalars['String']['input']; email: Scalars['String']['input'];
locale?: InputMaybe<Scalars['String']['input']>; locale?: InputMaybe<Scalars['String']['input']>;
@ -1666,7 +1678,6 @@ export type Query = {
getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
index: Index; index: Index;
indexMetadatas: IndexConnection; indexMetadatas: IndexConnection;
listAvailableWorkspaces: Array<AvailableWorkspaceOutput>;
object: Object; object: Object;
objects: ObjectConnection; objects: ObjectConnection;
plans: Array<BillingPlanOutput>; plans: Array<BillingPlanOutput>;
@ -1744,7 +1755,7 @@ export type QueryGetIndicatorHealthStatusArgs = {
export type QueryGetPublicWorkspaceDataByDomainArgs = { export type QueryGetPublicWorkspaceDataByDomainArgs = {
origin: Scalars['String']['input']; origin?: InputMaybe<Scalars['String']['input']>;
}; };
@ -1798,12 +1809,6 @@ export type QueryIndexMetadatasArgs = {
}; };
export type QueryListAvailableWorkspacesArgs = {
captchaToken?: InputMaybe<Scalars['String']['input']>;
email: Scalars['String']['input'];
};
export type QueryObjectArgs = { export type QueryObjectArgs = {
id: Scalars['UUID']['input']; id: Scalars['UUID']['input'];
}; };
@ -2064,6 +2069,7 @@ export enum SettingPermissionType {
DATA_MODEL = 'DATA_MODEL', DATA_MODEL = 'DATA_MODEL',
ROLES = 'ROLES', ROLES = 'ROLES',
SECURITY = 'SECURITY', SECURITY = 'SECURITY',
WORKFLOWS = 'WORKFLOWS',
WORKSPACE = 'WORKSPACE', WORKSPACE = 'WORKSPACE',
WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS'
} }
@ -2152,10 +2158,15 @@ export enum SubscriptionStatus {
export type Support = { export type Support = {
__typename?: 'Support'; __typename?: 'Support';
supportDriver: Scalars['String']['output']; supportDriver: SupportDriver;
supportFrontChatId?: Maybe<Scalars['String']['output']>; supportFrontChatId?: Maybe<Scalars['String']['output']>;
}; };
export enum SupportDriver {
FRONT = 'FRONT',
NONE = 'NONE'
}
export type SystemHealth = { export type SystemHealth = {
__typename?: 'SystemHealth'; __typename?: 'SystemHealth';
services: Array<SystemHealthService>; services: Array<SystemHealthService>;
@ -2386,7 +2397,7 @@ export type UpsertSettingPermissionsInput = {
export type User = { export type User = {
__typename?: 'User'; __typename?: 'User';
availableWorkspaces: Array<AvailableWorkspacesToJoin>; availableWorkspaces: AvailableWorkspaces;
canAccessFullAdminPanel: Scalars['Boolean']['output']; canAccessFullAdminPanel: Scalars['Boolean']['output'];
canImpersonate: Scalars['Boolean']['output']; canImpersonate: Scalars['Boolean']['output'];
createdAt: Scalars['DateTime']['output']; createdAt: Scalars['DateTime']['output'];
@ -2406,7 +2417,7 @@ export type User = {
passwordHash?: Maybe<Scalars['String']['output']>; passwordHash?: Maybe<Scalars['String']['output']>;
supportUserHash?: Maybe<Scalars['String']['output']>; supportUserHash?: Maybe<Scalars['String']['output']>;
updatedAt: Scalars['DateTime']['output']; updatedAt: Scalars['DateTime']['output'];
userVars: Scalars['JSONObject']['output']; userVars?: Maybe<Scalars['JSONObject']['output']>;
workspaceMember?: Maybe<WorkspaceMember>; workspaceMember?: Maybe<WorkspaceMember>;
workspaceMembers?: Maybe<Array<WorkspaceMember>>; workspaceMembers?: Maybe<Array<WorkspaceMember>>;
workspaces: Array<UserWorkspace>; workspaces: Array<UserWorkspace>;

View File

@ -1629,7 +1629,7 @@ export type QueryGetIndicatorHealthStatusArgs = {
export type QueryGetPublicWorkspaceDataByDomainArgs = { export type QueryGetPublicWorkspaceDataByDomainArgs = {
origin: Scalars['String']; origin?: InputMaybe<Scalars['String']>;
}; };
@ -1907,6 +1907,7 @@ export enum SettingPermissionType {
DATA_MODEL = 'DATA_MODEL', DATA_MODEL = 'DATA_MODEL',
ROLES = 'ROLES', ROLES = 'ROLES',
SECURITY = 'SECURITY', SECURITY = 'SECURITY',
WORKFLOWS = 'WORKFLOWS',
WORKSPACE = 'WORKSPACE', WORKSPACE = 'WORKSPACE',
WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS' WORKSPACE_MEMBERS = 'WORKSPACE_MEMBERS'
} }
@ -1995,10 +1996,15 @@ export enum SubscriptionStatus {
export type Support = { export type Support = {
__typename?: 'Support'; __typename?: 'Support';
supportDriver: Scalars['String']; supportDriver: SupportDriver;
supportFrontChatId?: Maybe<Scalars['String']>; supportFrontChatId?: Maybe<Scalars['String']>;
}; };
export enum SupportDriver {
FRONT = 'FRONT',
NONE = 'NONE'
}
export type SystemHealth = { export type SystemHealth = {
__typename?: 'SystemHealth'; __typename?: 'SystemHealth';
services: Array<SystemHealthService>; services: Array<SystemHealthService>;
@ -2741,7 +2747,7 @@ export type GetMeteredProductsUsageQuery = { __typename?: 'Query', getMeteredPro
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; 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<{ export type SearchQueryVariables = Exact<{
searchInput: Scalars['String']; searchInput: Scalars['String'];

View File

@ -15,6 +15,7 @@ import { isMultiWorkspaceEnabledState } from '@/client-config/states/isMultiWork
import { renderHook } from '@testing-library/react'; import { renderHook } from '@testing-library/react';
import { iconsState } from 'twenty-ui/display'; import { iconsState } from 'twenty-ui/display';
import { email, mocks, password, results, token } from '../__mocks__/useAuth'; import { email, mocks, password, results, token } from '../__mocks__/useAuth';
import { SupportDriver } from '~/generated/graphql';
const redirectSpy = jest.fn(); const redirectSpy = jest.fn();
@ -160,7 +161,7 @@ describe('useAuth', () => {
expect(state.billing).toBeNull(); expect(state.billing).toBeNull();
expect(state.isDeveloperDefaultSignInPrefilled).toBe(false); expect(state.isDeveloperDefaultSignInPrefilled).toBe(false);
expect(state.supportChat).toEqual({ expect(state.supportChat).toEqual({
supportDriver: 'none', supportDriver: SupportDriver.NONE,
supportFrontChatId: null, supportFrontChatId: null,
}); });
}); });

View File

@ -1,10 +1,10 @@
import { Support } from '~/generated/graphql';
import { createState } from 'twenty-ui/utilities'; import { createState } from 'twenty-ui/utilities';
import { Support, SupportDriver } from '~/generated/graphql';
export const supportChatState = createState<Support>({ export const supportChatState = createState<Support>({
key: 'supportChatState', key: 'supportChatState',
defaultValue: { defaultValue: {
supportDriver: 'none', supportDriver: SupportDriver.NONE,
supportFrontChatId: null, supportFrontChatId: null,
}, },
}); });

View File

@ -15,6 +15,7 @@ import {
} from '~/testing/mock-data/users'; } from '~/testing/mock-data/users';
import { SupportDropdown } from '@/support/components/SupportDropdown'; import { SupportDropdown } from '@/support/components/SupportDropdown';
import { SupportDriver } from '~/generated-metadata/graphql';
import { PrefetchLoadedDecorator } from '~/testing/decorators/PrefetchLoadedDecorator'; import { PrefetchLoadedDecorator } from '~/testing/decorators/PrefetchLoadedDecorator';
const meta: Meta<typeof SupportDropdown> = { const meta: Meta<typeof SupportDropdown> = {
@ -32,7 +33,10 @@ const meta: Meta<typeof SupportDropdown> = {
setCurrentWorkspace(mockCurrentWorkspace); setCurrentWorkspace(mockCurrentWorkspace);
setCurrentWorkspaceMember(mockedWorkspaceMemberData); setCurrentWorkspaceMember(mockedWorkspaceMemberData);
setCurrentUser(mockedUserData); setCurrentUser(mockedUserData);
setSupportChat({ supportDriver: 'front', supportFrontChatId: '1234' }); setSupportChat({
supportDriver: SupportDriver.FRONT,
supportFrontChatId: '1234',
});
return <Story />; return <Story />;
}, },

View File

@ -5,8 +5,8 @@ import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { isNonEmptyString } from '@sniptt/guards'; import { isNonEmptyString } from '@sniptt/guards';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { User, WorkspaceMember } from '~/generated-metadata/graphql';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { User, WorkspaceMember } from '~/generated-metadata/graphql';
const insertScript = ({ const insertScript = ({
src, src,
@ -74,7 +74,7 @@ export const useSupportChat = () => {
useEffect(() => { useEffect(() => {
if ( if (
supportChat?.supportDriver === 'front' && supportChat?.supportDriver === 'FRONT' &&
isNonEmptyString(supportChat.supportFrontChatId) && isNonEmptyString(supportChat.supportFrontChatId) &&
isNonEmptyString(currentUser?.email) && isNonEmptyString(currentUser?.email) &&
isDefined(currentWorkspaceMember) && isDefined(currentWorkspaceMember) &&

View File

@ -1,4 +1,8 @@
import { CaptchaDriverType, ClientConfig } from '~/generated/graphql'; import {
CaptchaDriverType,
ClientConfig,
SupportDriver,
} from '~/generated/graphql';
export const mockedClientConfig: ClientConfig = { export const mockedClientConfig: ClientConfig = {
signInPrefilled: true, signInPrefilled: true,
@ -17,7 +21,7 @@ export const mockedClientConfig: ClientConfig = {
debugMode: false, debugMode: false,
analyticsEnabled: true, analyticsEnabled: true,
support: { support: {
supportDriver: 'front', supportDriver: SupportDriver.FRONT,
supportFrontChatId: null, supportFrontChatId: null,
}, },
sentry: { sentry: {

View File

@ -1,5 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing'; 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 { ClientConfigService } from 'src/engine/core-modules/client-config/services/client-config.service';
import { ClientConfigController } from './client-config.controller'; import { ClientConfigController } from './client-config.controller';
@ -56,7 +58,7 @@ describe('ClientConfigController', () => {
frontDomain: 'localhost', frontDomain: 'localhost',
debugMode: true, debugMode: true,
support: { support: {
supportDriver: 'none', supportDriver: SupportDriver.NONE,
supportFrontChatId: undefined, supportFrontChatId: undefined,
}, },
sentry: { sentry: {

View File

@ -1,5 +1,7 @@
import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; 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 { BillingTrialPeriodDTO } from 'src/engine/core-modules/billing/dtos/billing-trial-period.dto';
import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces'; import { CaptchaDriverType } from 'src/engine/core-modules/captcha/interfaces';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
@ -23,8 +25,8 @@ class Billing {
@ObjectType() @ObjectType()
class Support { class Support {
@Field(() => String) @Field(() => SupportDriver)
supportDriver: string; supportDriver: SupportDriver;
@Field(() => String, { nullable: true }) @Field(() => String, { nullable: true })
supportFrontChatId?: string; supportFrontChatId?: string;

View File

@ -1,4 +1,10 @@
import { registerEnumType } from '@nestjs/graphql';
export enum SupportDriver { export enum SupportDriver {
NONE = 'NONE', NONE = 'NONE',
FRONT = 'FRONT', FRONT = 'FRONT',
} }
registerEnumType(SupportDriver, {
name: 'SupportDriver',
});