diff --git a/packages/twenty-front/src/generated-admin/graphql.ts b/packages/twenty-front/src/generated-admin/graphql.ts index 0973f9231a..8926f0f4d0 100644 --- a/packages/twenty-front/src/generated-admin/graphql.ts +++ b/packages/twenty-front/src/generated-admin/graphql.ts @@ -398,6 +398,7 @@ export type Mutation = { setAdminAiModelsRecommended: Scalars['Boolean']; setAdminDefaultAiModel: Scalars['Boolean']; setMaintenanceMode: Scalars['Boolean']; + updateAdminApplicationRegistrationVariable: ApplicationRegistrationVariableDto; updateDatabaseConfigVariable: Scalars['Boolean']; updateWorkspaceFeatureFlag: Scalars['Boolean']; }; @@ -491,6 +492,11 @@ export type MutationSetMaintenanceModeArgs = { }; +export type MutationUpdateAdminApplicationRegistrationVariableArgs = { + input: UpdateApplicationRegistrationVariableInput; +}; + + export type MutationUpdateDatabaseConfigVariableArgs = { key: Scalars['String']; value: Scalars['JSON']; @@ -731,6 +737,17 @@ export type SystemHealthService = { status: AdminPanelHealthServiceStatus; }; +export type UpdateApplicationRegistrationVariableInput = { + id: Scalars['String']; + update: UpdateApplicationRegistrationVariablePayload; +}; + +export type UpdateApplicationRegistrationVariablePayload = { + description?: InputMaybe; + resetValue?: InputMaybe; + value?: InputMaybe; +}; + export enum UpgradeHealth { BEHIND = 'BEHIND', FAILED = 'FAILED', @@ -922,6 +939,13 @@ export type GetModelsDevSuggestionsQueryVariables = Exact<{ export type GetModelsDevSuggestionsQuery = { __typename?: 'Query', getModelsDevSuggestions: Array<{ __typename?: 'ModelsDevModelSuggestion', modelId: string, name: string, inputCostPerMillionTokens: number, outputCostPerMillionTokens: number, cachedInputCostPerMillionTokens?: number | null, cacheCreationCostPerMillionTokens?: number | null, contextWindowTokens: number, maxOutputTokens: number, modalities: Array, supportsReasoning: boolean }> }; +export type UpdateAdminApplicationRegistrationVariableMutationVariables = Exact<{ + input: UpdateApplicationRegistrationVariableInput; +}>; + + +export type UpdateAdminApplicationRegistrationVariableMutation = { __typename?: 'Mutation', updateAdminApplicationRegistrationVariable: { __typename?: 'ApplicationRegistrationVariableDTO', id: string, key: string, description: string, isSecret: boolean, isRequired: boolean, isFilled: boolean, createdAt: string, updatedAt: string } }; + export type FindAdminApplicationRegistrationVariablesQueryVariables = Exact<{ applicationRegistrationId: Scalars['String']; }>; @@ -1153,6 +1177,7 @@ export const GetAdminAiUsageByWorkspaceDocument = {"kind":"Document","definition export const GetAiProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetAiProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getAiProviders"}}]}}]} as unknown as DocumentNode; export const GetModelsDevProvidersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevProviders"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"modelCount"}},{"kind":"Field","name":{"kind":"Name","value":"npm"}}]}}]}}]} as unknown as DocumentNode; export const GetModelsDevSuggestionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetModelsDevSuggestions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"getModelsDevSuggestions"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"providerType"},"value":{"kind":"Variable","name":{"kind":"Name","value":"providerType"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"inputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"outputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cachedInputCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"cacheCreationCostPerMillionTokens"}},{"kind":"Field","name":{"kind":"Name","value":"contextWindowTokens"}},{"kind":"Field","name":{"kind":"Name","value":"maxOutputTokens"}},{"kind":"Field","name":{"kind":"Name","value":"modalities"}},{"kind":"Field","name":{"kind":"Name","value":"supportsReasoning"}}]}}]}}]} as unknown as DocumentNode; +export const UpdateAdminApplicationRegistrationVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateAdminApplicationRegistrationVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateApplicationRegistrationVariableInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateAdminApplicationRegistrationVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const FindAdminApplicationRegistrationVariablesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAdminApplicationRegistrationVariables"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAdminApplicationRegistrationVariables"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRegistrationId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRegistrationId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isSecret"}},{"kind":"Field","name":{"kind":"Name","value":"isRequired"}},{"kind":"Field","name":{"kind":"Name","value":"isFilled"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode; export const FindAllApplicationRegistrationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"findAllApplicationRegistrations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ApplicationRegistrationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ApplicationRegistrationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ApplicationRegistration"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"logoUrl"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthClientId"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthRedirectUris"}},{"kind":"Field","name":{"kind":"Name","value":"oAuthScopes"}},{"kind":"Field","name":{"kind":"Name","value":"sourceType"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePackage"}},{"kind":"Field","name":{"kind":"Name","value":"latestAvailableVersion"}},{"kind":"Field","name":{"kind":"Name","value":"isListed"}},{"kind":"Field","name":{"kind":"Name","value":"isFeatured"}},{"kind":"Field","name":{"kind":"Name","value":"isPreInstalled"}},{"kind":"Field","name":{"kind":"Name","value":"isConfigured"}},{"kind":"Field","name":{"kind":"Name","value":"ownerWorkspaceId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const CreateDatabaseConfigVariableDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateDatabaseConfigVariable"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"key"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"value"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createDatabaseConfigVariable"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"key"},"value":{"kind":"Variable","name":{"kind":"Name","value":"key"}}},{"kind":"Argument","name":{"kind":"Name","value":"value"},"value":{"kind":"Variable","name":{"kind":"Name","value":"value"}}}]}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index cf7cc1b22d..e4c1944f6a 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -242,6 +242,14 @@ const SettingsAdminApplicationRegistrationDetail = lazy(() => ), ); +const SettingsAdminApplicationRegistrationConfigVariableDetail = lazy(() => + import('~/pages/settings/admin-panel/SettingsAdminApplicationRegistrationConfigVariableDetail').then( + (module) => ({ + default: module.SettingsAdminApplicationRegistrationConfigVariableDetail, + }), + ), +); + const SettingsAvailableApplicationDetails = lazy(() => import('~/pages/settings/applications/SettingsAvailableApplicationDetails').then( (module) => ({ @@ -965,6 +973,14 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( path={SettingsPath.AdminPanelApplicationRegistrationDetail} element={} /> + + } + /> } diff --git a/packages/twenty-front/src/modules/settings/admin-panel/apps/graphql/mutations/updateAdminApplicationRegistrationVariable.ts b/packages/twenty-front/src/modules/settings/admin-panel/apps/graphql/mutations/updateAdminApplicationRegistrationVariable.ts new file mode 100644 index 0000000000..17e352d8f9 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/admin-panel/apps/graphql/mutations/updateAdminApplicationRegistrationVariable.ts @@ -0,0 +1,18 @@ +import { gql } from '@apollo/client'; + +export const UPDATE_ADMIN_APPLICATION_REGISTRATION_VARIABLE = gql` + mutation UpdateAdminApplicationRegistrationVariable( + $input: UpdateApplicationRegistrationVariableInput! + ) { + updateAdminApplicationRegistrationVariable(input: $input) { + id + key + description + isSecret + isRequired + isFilled + createdAt + updatedAt + } + } +`; diff --git a/packages/twenty-front/src/modules/settings/application-registrations/components/ApplicationRegistrationConfigVariableEditForm.tsx b/packages/twenty-front/src/modules/settings/application-registrations/components/ApplicationRegistrationConfigVariableEditForm.tsx new file mode 100644 index 0000000000..f2fb7804f6 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/application-registrations/components/ApplicationRegistrationConfigVariableEditForm.tsx @@ -0,0 +1,78 @@ +import { useState } from 'react'; +import { useLingui } from '@lingui/react/macro'; +import { isNonEmptyString } from '@sniptt/guards'; +import { ConfigVariableEdit } from '@/settings/config-variables/components/ConfigVariableEdit'; +import { TextInput } from '@/ui/input/components/TextInput'; + +type VariableData = { + id: string; + key: string; + value?: string | null; + description: string; + isFilled: boolean; +}; + +export const ApplicationRegistrationConfigVariableEditForm = ({ + variable, + onUpdateVariable, +}: { + variable: VariableData; + onUpdateVariable: ( + id: string, + update: { value: string; resetValue?: boolean }, + ) => Promise; +}) => { + const { t } = useLingui(); + + const [value, setValue] = useState(''); + const [isEditing, setIsEditing] = useState(false); + + const canOpenCancelModal = variable.isFilled && !isNonEmptyString(value); + + const onCancel = () => { + setValue(''); + }; + + const onSave = async () => { + if (!isNonEmptyString(value)) { + return; + } + + try { + await onUpdateVariable(variable.id, { value }); + } finally { + setValue(''); + } + }; + + const onConfirmReset = async () => { + try { + await onUpdateVariable(variable.id, { value: '', resetValue: true }); + } finally { + setValue(''); + } + }; + + return ( + + } + isEditing={isEditing} + setIsEditing={setIsEditing} + isSaveDisabled={!isNonEmptyString(value)} + onSave={onSave} + onCancel={onCancel} + canOpenCancelModal={canOpenCancelModal} + onConfirmReset={onConfirmReset} + /> + ); +}; diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationConfigVariableDetail.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationConfigVariableDetail.tsx new file mode 100644 index 0000000000..8ff039323a --- /dev/null +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminApplicationRegistrationConfigVariableDetail.tsx @@ -0,0 +1,106 @@ +import { useLingui } from '@lingui/react/macro'; +import { useParams } from 'react-router-dom'; +import { useMutation, useQuery } from '@apollo/client/react'; +import { + FindAdminApplicationRegistrationVariablesDocument, + FindOneAdminApplicationRegistrationDocument, + UpdateAdminApplicationRegistrationVariableDocument, +} from '~/generated-admin/graphql'; +import { useApolloAdminClient } from '@/settings/admin-panel/apollo/hooks/useApolloAdminClient'; +import { APPLICATION_REGISTRATION_ADMIN_PATH } from '@/settings/admin-panel/apps/constants/ApplicationRegistrationAdminPath'; +import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { getSettingsPath } from 'twenty-shared/utils'; +import { SettingsPath } from 'twenty-shared/types'; +import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; +import { NotFound } from '~/pages/not-found/NotFound'; +import { ApplicationRegistrationConfigVariableEditForm } from '@/settings/application-registrations/components/ApplicationRegistrationConfigVariableEditForm'; + +export const SettingsAdminApplicationRegistrationConfigVariableDetail = () => { + const { t } = useLingui(); + const apolloAdminClient = useApolloAdminClient(); + + const { variableKey, applicationRegistrationId = '' } = useParams<{ + applicationRegistrationId: string; + variableKey: string; + }>(); + + const { data: applicationRegistrationData, loading: registrationLoading } = + useQuery(FindOneAdminApplicationRegistrationDocument, { + client: apolloAdminClient, + variables: { id: applicationRegistrationId }, + skip: !applicationRegistrationId, + }); + + const registration = + applicationRegistrationData?.findOneAdminApplicationRegistration; + + const { data: variablesData, loading: variablesLoading } = useQuery( + FindAdminApplicationRegistrationVariablesDocument, + { + client: apolloAdminClient, + variables: { applicationRegistrationId }, + skip: !applicationRegistrationId, + }, + ); + + const variable = ( + variablesData?.findAdminApplicationRegistrationVariables ?? [] + ).find((variable) => variable.key === variableKey); + + const [updateVariable] = useMutation( + UpdateAdminApplicationRegistrationVariableDocument, + { + client: apolloAdminClient, + refetchQueries: [FindAdminApplicationRegistrationVariablesDocument], + }, + ); + + if (registrationLoading || variablesLoading) { + return ; + } + + if (!variable || !registration) { + return ; + } + + const onUpdateVariable = async ( + id: string, + update: { value: string; resetValue?: boolean }, + ) => { + await updateVariable({ + variables: { input: { id, update } }, + }); + }; + + return ( + + + + ); +}; diff --git a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationRegistrationConfigVariableDetail.tsx b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationRegistrationConfigVariableDetail.tsx index 7afd81948a..72184c8ece 100644 --- a/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationRegistrationConfigVariableDetail.tsx +++ b/packages/twenty-front/src/pages/settings/applications/components/SettingsApplicationRegistrationConfigVariableDetail.tsx @@ -1,45 +1,36 @@ import { useLingui } from '@lingui/react/macro'; import { useParams } from 'react-router-dom'; -import { useState } from 'react'; import { FindApplicationRegistrationVariablesDocument, FindOneApplicationRegistrationDocument, UpdateApplicationRegistrationVariableDocument, } from '~/generated-metadata/graphql'; import { useMutation, useQuery } from '@apollo/client/react'; -import { isNonEmptyString } from '@sniptt/guards'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { getSettingsPath } from 'twenty-shared/utils'; import { SettingsPath } from 'twenty-shared/types'; -import { ConfigVariableEdit } from '@/settings/config-variables/components/ConfigVariableEdit'; -import { TextInput } from '@/ui/input/components/TextInput'; import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader'; +import { NotFound } from '~/pages/not-found/NotFound'; +import { ApplicationRegistrationConfigVariableEditForm } from '@/settings/application-registrations/components/ApplicationRegistrationConfigVariableEditForm'; export const SettingsApplicationRegistrationConfigVariableDetail = () => { const { t } = useLingui(); - const { variableKey } = useParams(); - - const [value, setValue] = useState(''); - - const [isEditing, setIsEditing] = useState(false); - - const { applicationRegistrationId = '' } = useParams<{ + const { variableKey, applicationRegistrationId = '' } = useParams<{ applicationRegistrationId: string; + variableKey: string; }>(); - const { data: applicationRegistrationData } = useQuery( - FindOneApplicationRegistrationDocument, - { + const { data: applicationRegistrationData, loading: registrationLoading } = + useQuery(FindOneApplicationRegistrationDocument, { variables: { id: applicationRegistrationId }, skip: !applicationRegistrationId, - }, - ); + }); const registration = applicationRegistrationData?.findOneApplicationRegistration; - const { data: variablesData } = useQuery( + const { data: variablesData, loading: variablesLoading } = useQuery( FindApplicationRegistrationVariablesDocument, { variables: { applicationRegistrationId }, @@ -58,53 +49,21 @@ export const SettingsApplicationRegistrationConfigVariableDetail = () => { }, ); - if (!variable || !registration) { + if (registrationLoading || variablesLoading) { return ; } - const canOpenCancelModal = variable.isFilled && !isNonEmptyString(value); + if (!variable || !registration) { + return ; + } - const onCancel = () => { - setValue(''); - }; - - const onSave = async () => { - if (!isNonEmptyString(value)) { - return; - } - - try { - await updateVariable({ - variables: { - input: { - id: variable.id, - update: { - value, - }, - }, - }, - }); - } finally { - setValue(''); - } - }; - - const onConfirmReset = async () => { - try { - await updateVariable({ - variables: { - input: { - id: variable.id, - update: { - value: '', - resetValue: true, - }, - }, - }, - }); - } finally { - setValue(''); - } + const onUpdateVariable = async ( + id: string, + update: { value: string; resetValue?: boolean }, + ) => { + await updateVariable({ + variables: { input: { id, update } }, + }); }; return ( @@ -137,27 +96,9 @@ export const SettingsApplicationRegistrationConfigVariableDetail = () => { }, ]} > - - } - isEditing={isEditing} - setIsEditing={setIsEditing} - isSaveDisabled={!isNonEmptyString(value)} - onSave={onSave} - onCancel={onCancel} - canOpenCancelModal={canOpenCancelModal} - onConfirmReset={onConfirmReset} + ); diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationRegistrationConfigTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationRegistrationConfigTab.tsx index 03dc2c6461..4850e01925 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationRegistrationConfigTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationRegistrationConfigTab.tsx @@ -48,7 +48,9 @@ export const SettingsApplicationRegistrationConfigTab = ({ description: variable.description, value: variable.value ?? , to: getSettingsPath( - SettingsPath.ApplicationRegistrationConfigVariableDetails, + fromAdmin + ? SettingsPath.AdminPanelApplicationRegistrationConfigVariableDetails + : SettingsPath.ApplicationRegistrationConfigVariableDetails, { applicationRegistrationId, variableKey: variable.key, diff --git a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts index 07dc6f4f45..87bcd3a53a 100644 --- a/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/admin-panel/admin-panel.resolver.ts @@ -46,6 +46,7 @@ import { AdminPanelUserLookupService } from 'src/engine/core-modules/admin-panel import { AdminPanelVersionService } from 'src/engine/core-modules/admin-panel/services/admin-panel-version.service'; import { ApplicationRegistrationVariableDTO } from 'src/engine/core-modules/application/application-registration-variable/dtos/application-registration-variable.dto'; import { ApplicationRegistrationVariableService } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.service'; +import { UpdateApplicationRegistrationVariableInput } from 'src/engine/core-modules/application/application-registration-variable/dtos/update-application-registration-variable.input'; import { ApplicationRegistrationEntity } from 'src/engine/core-modules/application/application-registration/application-registration.entity'; import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service'; import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter'; @@ -725,6 +726,16 @@ export class AdminPanelResolver { ); } + @UseGuards(AdminPanelGuard) + @Mutation(() => ApplicationRegistrationVariableDTO) + async updateAdminApplicationRegistrationVariable( + @Args('input') input: UpdateApplicationRegistrationVariableInput, + ): Promise { + return this.applicationRegistrationVariableService.updateVariableGlobal( + input, + ); + } + @UseGuards(AdminPanelGuard) @Query(() => InstanceAndAllWorkspacesUpgradeStatusDTO) async getInstanceAndAllWorkspacesUpgradeStatus(): Promise { diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration-variable/application-registration-variable.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration-variable/application-registration-variable.service.ts index a7bc4a9c76..1747cc8df9 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-registration-variable/application-registration-variable.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-registration-variable/application-registration-variable.service.ts @@ -48,20 +48,7 @@ export class ApplicationRegistrationVariableService { order: { key: 'ASC' }, }); - return variables.map((variable) => { - const { encryptedValue } = variable; - - return { - ...variable, - isFilled: variable.isFilled, - value: - encryptedValue !== '' - ? variable.isSecret - ? '•••••••••••••' - : this.encryptionService.decryptVersioned(encryptedValue) - : null, - }; - }); + return variables.map((variable) => this.toObfuscatedDTO(variable)); } async createVariable( @@ -90,58 +77,28 @@ export class ApplicationRegistrationVariableService { input: UpdateApplicationRegistrationVariableInput, workspaceId: string, ): Promise { - const { id, update } = input; - - const variable = await this.variableRepository.findOne({ - where: { id }, - }); - - if (!variable) { - throw new ApplicationRegistrationException( - `Variable with id ${id} not found`, - ApplicationRegistrationExceptionCode.VARIABLE_NOT_FOUND, - ); - } + const variable = await this.findVariableOrThrow(input.id); await this.assertRegistrationOwnedByWorkspace( variable.applicationRegistrationId, workspaceId, ); - const updateData: Record = {}; + return this.applyVariableUpdate(input); + } - if (isDefined(update.value)) { - updateData.encryptedValue = this.encryptionService.encryptVersioned( - update.value, - ); - } + async updateVariableGlobal( + input: UpdateApplicationRegistrationVariableInput, + ): Promise { + await this.findVariableOrThrow(input.id); - if (isDefined(update.resetValue) && update.resetValue) { - updateData.encryptedValue = ''; - } + const entity = await this.applyVariableUpdate(input); - if (isDefined(update.description)) { - updateData.description = update.description; - } - - if (Object.keys(updateData).length > 0) { - await this.variableRepository.update(id, updateData); - } - - return this.variableRepository.findOneOrFail({ where: { id } }); + return this.toObfuscatedDTO(entity); } async deleteVariable(id: string, workspaceId: string): Promise { - const variable = await this.variableRepository.findOne({ - where: { id }, - }); - - if (!variable) { - throw new ApplicationRegistrationException( - `Variable with id ${id} not found`, - ApplicationRegistrationExceptionCode.VARIABLE_NOT_FOUND, - ); - } + const variable = await this.findVariableOrThrow(id); await this.assertRegistrationOwnedByWorkspace( variable.applicationRegistrationId, @@ -241,6 +198,68 @@ export class ApplicationRegistrationVariableService { return result; } + private async findVariableOrThrow( + id: string, + ): Promise { + const variable = await this.variableRepository.findOne({ + where: { id }, + }); + + if (!variable) { + throw new ApplicationRegistrationException( + `Variable with id ${id} not found`, + ApplicationRegistrationExceptionCode.VARIABLE_NOT_FOUND, + ); + } + + return variable; + } + + private async applyVariableUpdate( + input: UpdateApplicationRegistrationVariableInput, + ): Promise { + const { id, update } = input; + + const updateData: Record = {}; + + if (isDefined(update.value)) { + updateData.encryptedValue = this.encryptionService.encryptVersioned( + update.value, + ); + } + + if (isDefined(update.resetValue) && update.resetValue) { + updateData.encryptedValue = ''; + } + + if (isDefined(update.description)) { + updateData.description = update.description; + } + + if (Object.keys(updateData).length > 0) { + await this.variableRepository.update(id, updateData); + } + + return this.variableRepository.findOneOrFail({ where: { id } }); + } + + private toObfuscatedDTO( + variable: ApplicationRegistrationVariableEntity, + ): ApplicationRegistrationVariableDTO { + const { encryptedValue } = variable; + + return { + ...variable, + isFilled: variable.isFilled, + value: + encryptedValue !== '' + ? variable.isSecret + ? '•••••••••••••' + : this.encryptionService.decryptVersioned(encryptedValue) + : null, + }; + } + private async assertRegistrationOwnedByWorkspace( registrationId: string, workspaceId: string, diff --git a/packages/twenty-shared/src/types/SettingsPath.ts b/packages/twenty-shared/src/types/SettingsPath.ts index cd0742d999..9e7a2651eb 100644 --- a/packages/twenty-shared/src/types/SettingsPath.ts +++ b/packages/twenty-shared/src/types/SettingsPath.ts @@ -84,6 +84,7 @@ export enum SettingsPath { AdminPanelUserDetail = 'admin-panel/users/:userId', AdminPanelWorkspaceDetail = 'admin-panel/workspaces/:workspaceId', AdminPanelApplicationRegistrationDetail = 'admin-panel/applications/registrations/:applicationRegistrationId', + AdminPanelApplicationRegistrationConfigVariableDetails = 'admin-panel/applications/registrations/:applicationRegistrationId/config-variables/:variableKey', AdminPanelWorkspaceChatThread = 'admin-panel/workspaces/:workspaceId/threads/:threadId', Roles = 'members/roles',