From e957b1acd6ea2a8d973c3a980331b9c7ac799598 Mon Sep 17 00:00:00 2001 From: nitin <142569587+ehconitin@users.noreply.github.com> Date: Wed, 30 Apr 2025 12:42:59 +0530 Subject: [PATCH] Twenty config admin panel integration (#11755) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes https://github.com/twentyhq/core-team-issues/issues/761 closes https://github.com/twentyhq/core-team-issues/issues/762 --------- Co-authored-by: Félix Malfait --- .github/workflows/preview-env-keepalive.yaml | 2 + .../twenty-front/src/generated/graphql.tsx | 319 +++++++++++++++--- .../modules/app/components/SettingsRoutes.tsx | 11 +- .../components/ClientConfigProviderEffect.tsx | 11 +- .../graphql/queries/getClientConfig.ts | 1 + .../isConfigVariablesInDbEnabledState.ts | 5 + .../components/SettingsAdminContent.tsx | 4 +- .../components/SettingsAdminEnvVariables.tsx | 68 ---- .../SettingsAdminEnvVariablesRow.tsx | 170 ---------- .../SettingsAdminEnvVariablesTable.tsx | 51 --- .../components/SettingsAdminTabContent.tsx | 6 +- .../ConfigVariableActionButtons.tsx | 62 ++++ .../ConfigVariableDatabaseInput.tsx | 190 +++++++++++ .../ConfigVariableFilterContainer.tsx | 44 +++ .../ConfigVariableFilterDropdown.tsx | 66 ++++ .../components/ConfigVariableHelpText.tsx | 85 +++++ .../ConfigVariableOptionsDropdownContent.tsx | 141 ++++++++ .../components/ConfigVariableSearchInput.tsx | 28 ++ .../components/ConfigVariableValueInput.tsx | 57 ++++ .../SettingsAdminConfigCopyableText.tsx} | 18 +- .../SettingsAdminConfigVariables.tsx | 207 ++++++++++++ .../SettingsAdminConfigVariablesRow.tsx | 70 ++++ .../SettingsAdminConfigVariablesTable.tsx | 37 ++ .../constants/ConfigVariableSourceOptions.ts | 15 + .../mutations/createDatabaseConfigVariable.ts | 7 + .../mutations/deleteDatabaseConfigVariable.ts | 7 + .../mutations/updateDatabaseConfigVariable.ts | 7 + .../queries/getConfigVariablesGrouped.ts | 4 + .../queries/getDatabaseConfigVariable.ts | 16 + .../hooks/useConfigVariableActions.ts | 115 +++++++ .../hooks/useConfigVariableForm.ts | 54 +++ .../types/ConfigVariableFilterCategory.ts | 1 + .../types/ConfigVariableGroupFilter.ts | 1 + .../types/ConfigVariableOptions.ts | 3 + .../types/ConfigVariableSourceFilter.ts | 5 + .../utils/useSourceContent.ts | 29 ++ .../constants/SettingsAdminTabs.ts | 2 +- .../SettingsDataModelFieldBooleanForm.tsx | 2 +- .../src/modules/types/SettingsPath.ts | 2 +- .../SettingsAdminConfigVariableDetails.tsx | 211 ++++++++++++ .../SettingsAdminSecondaryEnvVariables.tsx | 60 ---- .../src/testing/mock-data/config.ts | 1 + .../src/utils/navigation/getSettingsPath.ts | 5 + .../__tests__/admin-panel.service.spec.ts | 80 ++++- .../admin-panel/admin-panel.resolver.ts | 56 ++- .../admin-panel/admin-panel.service.ts | 35 +- .../admin-panel/dtos/config-variable.dto.ts | 33 +- .../client-config/client-config.entity.ts | 3 + .../client-config/client-config.resolver.ts | 3 + .../cache/config-cache.service.ts | 3 +- .../twenty-config/config-variables.ts | 257 +++++++------- .../config-value-converter.service.spec.ts | 33 +- .../config-value-converter.service.ts | 20 +- .../config-variables-metadata.decorator.ts | 18 +- .../__tests__/database-config.driver.spec.ts | 47 --- .../drivers/database-config.driver.ts | 123 +++---- .../drivers/database-config.module.ts | 2 + .../database-config-driver.interface.ts | 5 - .../enums/config-variable-type.enum.ts | 7 + ...g-variable-graphql-api-exception.filter.ts | 31 ++ .../__tests__/config-storage.service.spec.ts | 314 +++++++++++++++++ .../storage/config-storage.service.ts | 122 ++++--- .../twenty-config/twenty-config.exception.ts | 17 + .../twenty-config.service.spec.ts | 80 ++++- .../twenty-config/twenty-config.service.ts | 212 ++++++++---- .../types/config-variable-type.type.ts | 6 - .../apply-basic-validators.util.spec.ts | 45 ++- .../utils/apply-basic-validators.util.ts | 21 +- .../src/types/ConfigVariableValue.ts | 1 + packages/twenty-shared/src/types/index.ts | 1 + .../display/icon/components/TablerIcons.ts | 1 + packages/twenty-ui/src/display/index.ts | 1 + .../display/typography/components/H3Title.tsx | 34 +- 73 files changed, 2958 insertions(+), 853 deletions(-) create mode 100644 packages/twenty-front/src/modules/client-config/states/isConfigVariablesInDbEnabledState.ts delete mode 100644 packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariables.tsx delete mode 100644 packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariablesRow.tsx delete mode 100644 packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariablesTable.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableActionButtons.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableDatabaseInput.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableFilterContainer.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableFilterDropdown.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableHelpText.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableOptionsDropdownContent.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableSearchInput.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx rename packages/twenty-front/src/modules/settings/admin-panel/{components/SettingsAdminEnvCopyableText.tsx => config-variables/components/SettingsAdminConfigCopyableText.tsx} (92%) create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariablesRow.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/SettingsAdminConfigVariablesTable.tsx create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/constants/ConfigVariableSourceOptions.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/graphql/mutations/createDatabaseConfigVariable.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/graphql/mutations/deleteDatabaseConfigVariable.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/graphql/mutations/updateDatabaseConfigVariable.ts rename packages/twenty-front/src/modules/settings/admin-panel/{ => config-variables}/graphql/queries/getConfigVariablesGrouped.ts (83%) create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/graphql/queries/getDatabaseConfigVariable.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/hooks/useConfigVariableActions.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/hooks/useConfigVariableForm.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/types/ConfigVariableFilterCategory.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/types/ConfigVariableGroupFilter.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/types/ConfigVariableOptions.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/types/ConfigVariableSourceFilter.ts create mode 100644 packages/twenty-front/src/modules/settings/admin-panel/config-variables/utils/useSourceContent.ts create mode 100644 packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx delete mode 100644 packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminSecondaryEnvVariables.tsx create mode 100644 packages/twenty-server/src/engine/core-modules/twenty-config/enums/config-variable-type.enum.ts create mode 100644 packages/twenty-server/src/engine/core-modules/twenty-config/filters/config-variable-graphql-api-exception.filter.ts create mode 100644 packages/twenty-server/src/engine/core-modules/twenty-config/twenty-config.exception.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/twenty-config/types/config-variable-type.type.ts create mode 100644 packages/twenty-shared/src/types/ConfigVariableValue.ts diff --git a/.github/workflows/preview-env-keepalive.yaml b/.github/workflows/preview-env-keepalive.yaml index 5f759bc35..251dcb9d5 100644 --- a/.github/workflows/preview-env-keepalive.yaml +++ b/.github/workflows/preview-env-keepalive.yaml @@ -33,6 +33,8 @@ jobs: echo "# === Randomly generated secrets ===" >> packages/twenty-docker/.env echo "APP_SECRET=$(openssl rand -base64 32)" >> packages/twenty-docker/.env echo "PG_DATABASE_PASSWORD=$(openssl rand -hex 16)" >> packages/twenty-docker/.env + # Remove line below when true becomes the default value (soon) + echo "CONFIG_VARIABLES_IN_DB_ENABLED=true" >> packages/twenty-docker/.env echo "Docker compose build..." cd packages/twenty-docker/ diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index dab693ffe..c2c6b218f 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -301,6 +301,7 @@ export type ClientConfig = { defaultSubdomain?: Maybe; frontDomain: Scalars['String']; isAttachmentPreviewEnabled: Scalars['Boolean']; + isConfigVariablesInDbEnabled: Scalars['Boolean']; isEmailVerificationRequired: Scalars['Boolean']; isGoogleCalendarEnabled: Scalars['Boolean']; isGoogleMessagingEnabled: Scalars['Boolean']; @@ -318,14 +319,32 @@ export type ComputeStepOutputSchemaInput = { step: Scalars['JSON']; }; +export enum ConfigSource { + DATABASE = 'DATABASE', + DEFAULT = 'DEFAULT', + ENVIRONMENT = 'ENVIRONMENT' +} + export type ConfigVariable = { __typename?: 'ConfigVariable'; description: Scalars['String']; + isEnvOnly: Scalars['Boolean']; isSensitive: Scalars['Boolean']; name: Scalars['String']; - value: Scalars['String']; + options?: Maybe; + source: ConfigSource; + type: ConfigVariableType; + value?: Maybe; }; +export enum ConfigVariableType { + ARRAY = 'ARRAY', + BOOLEAN = 'BOOLEAN', + ENUM = 'ENUM', + NUMBER = 'NUMBER', + STRING = 'STRING' +} + export enum ConfigVariablesGroup { AnalyticsConfig = 'AnalyticsConfig', BillingConfig = 'BillingConfig', @@ -868,6 +887,7 @@ export type Mutation = { checkoutSession: BillingSessionOutput; computeStepOutputSchema: Scalars['JSON']; createApprovedAccessDomain: ApprovedAccessDomain; + createDatabaseConfigVariable: Scalars['Boolean']; createDraftFromWorkflowVersion: WorkflowVersion; createOIDCIdentityProvider: SetupSsoOutput; createOneAppToken: AppToken; @@ -880,6 +900,7 @@ export type Mutation = { deactivateWorkflowVersion: Scalars['Boolean']; deleteApprovedAccessDomain: Scalars['Boolean']; deleteCurrentWorkspace: Workspace; + deleteDatabaseConfigVariable: Scalars['Boolean']; deleteOneField: Field; deleteOneObject: Object; deleteOneRole: Scalars['String']; @@ -914,6 +935,7 @@ export type Mutation = { switchToYearlyInterval: BillingUpdateOutput; track: Analytics; trackAnalytics: Analytics; + updateDatabaseConfigVariable: Scalars['Boolean']; updateLabPublicFeatureFlag: FeatureFlagDto; updateOneField: Field; updateOneObject: Object; @@ -971,6 +993,12 @@ export type MutationCreateApprovedAccessDomainArgs = { }; +export type MutationCreateDatabaseConfigVariableArgs = { + key: Scalars['String']; + value: Scalars['JSON']; +}; + + export type MutationCreateDraftFromWorkflowVersionArgs = { input: CreateDraftFromWorkflowVersionInput; }; @@ -1016,6 +1044,11 @@ export type MutationDeleteApprovedAccessDomainArgs = { }; +export type MutationDeleteDatabaseConfigVariableArgs = { + key: Scalars['String']; +}; + + export type MutationDeleteOneFieldArgs = { input: DeleteOneFieldInput; }; @@ -1162,6 +1195,12 @@ export type MutationTrackAnalyticsArgs = { }; +export type MutationUpdateDatabaseConfigVariableArgs = { + key: Scalars['String']; + value: Scalars['JSON']; +}; + + export type MutationUpdateLabPublicFeatureFlagArgs = { input: UpdateLabPublicFeatureFlagInput; }; @@ -1485,6 +1524,7 @@ export type Query = { getApprovedAccessDomains: Array; getAvailablePackages: Scalars['JSON']; getConfigVariablesGrouped: ConfigVariablesOutput; + getDatabaseConfigVariable: ConfigVariable; getIndicatorHealthStatus: AdminPanelHealthServiceData; getMeteredProductsUsage: Array; getPostgresCredentials?: Maybe; @@ -1540,6 +1580,11 @@ export type QueryGetAvailablePackagesArgs = { }; +export type QueryGetDatabaseConfigVariableArgs = { + key: Scalars['String']; +}; + + export type QueryGetIndicatorHealthStatusArgs = { indicatorId: HealthIndicatorId; }; @@ -2665,7 +2710,7 @@ export type SwitchSubscriptionToYearlyIntervalMutation = { __typename?: 'Mutatio 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, 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: 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 SearchQueryVariables = Exact<{ searchInput: Scalars['String']; @@ -2683,6 +2728,41 @@ export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string] export type SkipSyncEmailOnboardingStepMutation = { __typename?: 'Mutation', skipSyncEmailOnboardingStep: { __typename?: 'OnboardingStepSuccess', success: boolean } }; +export type CreateDatabaseConfigVariableMutationVariables = Exact<{ + key: Scalars['String']; + value: Scalars['JSON']; +}>; + + +export type CreateDatabaseConfigVariableMutation = { __typename?: 'Mutation', createDatabaseConfigVariable: boolean }; + +export type DeleteDatabaseConfigVariableMutationVariables = Exact<{ + key: Scalars['String']; +}>; + + +export type DeleteDatabaseConfigVariableMutation = { __typename?: 'Mutation', deleteDatabaseConfigVariable: boolean }; + +export type UpdateDatabaseConfigVariableMutationVariables = Exact<{ + key: Scalars['String']; + value: Scalars['JSON']; +}>; + + +export type UpdateDatabaseConfigVariableMutation = { __typename?: 'Mutation', updateDatabaseConfigVariable: boolean }; + +export type GetConfigVariablesGroupedQueryVariables = Exact<{ [key: string]: never; }>; + + +export type GetConfigVariablesGroupedQuery = { __typename?: 'Query', getConfigVariablesGrouped: { __typename?: 'ConfigVariablesOutput', groups: Array<{ __typename?: 'ConfigVariablesGroupData', name: ConfigVariablesGroup, description: string, isHiddenOnLoad: boolean, variables: Array<{ __typename?: 'ConfigVariable', name: string, description: string, value?: any | null, isSensitive: boolean, isEnvOnly: boolean, type: ConfigVariableType, options?: any | null, source: ConfigSource }> }> } }; + +export type GetDatabaseConfigVariableQueryVariables = Exact<{ + key: Scalars['String']; +}>; + + +export type GetDatabaseConfigVariableQuery = { __typename?: 'Query', getDatabaseConfigVariable: { __typename?: 'ConfigVariable', name: string, description: string, value?: any | null, isSensitive: boolean, isEnvOnly: boolean, type: ConfigVariableType, options?: any | null, source: ConfigSource } }; + export type UpdateWorkspaceFeatureFlagMutationVariables = Exact<{ workspaceId: Scalars['String']; featureFlag: Scalars['String']; @@ -2699,11 +2779,6 @@ export type UserLookupAdminPanelMutationVariables = Exact<{ export type UserLookupAdminPanelMutation = { __typename?: 'Mutation', userLookupAdminPanel: { __typename?: 'UserLookup', user: { __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }, workspaces: Array<{ __typename?: 'WorkspaceInfo', id: string, name: string, logo?: string | null, totalUsers: number, allowImpersonation: boolean, users: Array<{ __typename?: 'UserInfo', id: string, email: string, firstName?: string | null, lastName?: string | null }>, featureFlags: Array<{ __typename?: 'FeatureFlag', key: FeatureFlagKey, value: boolean }> }> } }; -export type GetConfigVariablesGroupedQueryVariables = Exact<{ [key: string]: never; }>; - - -export type GetConfigVariablesGroupedQuery = { __typename?: 'Query', getConfigVariablesGrouped: { __typename?: 'ConfigVariablesOutput', groups: Array<{ __typename?: 'ConfigVariablesGroupData', name: ConfigVariablesGroup, description: string, isHiddenOnLoad: boolean, variables: Array<{ __typename?: 'ConfigVariable', name: string, description: string, value: string, isSensitive: boolean }> }> } }; - export type GetVersionInfoQueryVariables = Exact<{ [key: string]: never; }>; @@ -4510,6 +4585,7 @@ export const GetClientConfigDocument = gql` isMicrosoftCalendarEnabled isGoogleMessagingEnabled isGoogleCalendarEnabled + isConfigVariablesInDbEnabled } } `; @@ -4622,6 +4698,191 @@ export function useSkipSyncEmailOnboardingStepMutation(baseOptions?: Apollo.Muta export type SkipSyncEmailOnboardingStepMutationHookResult = ReturnType; export type SkipSyncEmailOnboardingStepMutationResult = Apollo.MutationResult; export type SkipSyncEmailOnboardingStepMutationOptions = Apollo.BaseMutationOptions; +export const CreateDatabaseConfigVariableDocument = gql` + mutation CreateDatabaseConfigVariable($key: String!, $value: JSON!) { + createDatabaseConfigVariable(key: $key, value: $value) +} + `; +export type CreateDatabaseConfigVariableMutationFn = Apollo.MutationFunction; + +/** + * __useCreateDatabaseConfigVariableMutation__ + * + * To run a mutation, you first call `useCreateDatabaseConfigVariableMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateDatabaseConfigVariableMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createDatabaseConfigVariableMutation, { data, loading, error }] = useCreateDatabaseConfigVariableMutation({ + * variables: { + * key: // value for 'key' + * value: // value for 'value' + * }, + * }); + */ +export function useCreateDatabaseConfigVariableMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateDatabaseConfigVariableDocument, options); + } +export type CreateDatabaseConfigVariableMutationHookResult = ReturnType; +export type CreateDatabaseConfigVariableMutationResult = Apollo.MutationResult; +export type CreateDatabaseConfigVariableMutationOptions = Apollo.BaseMutationOptions; +export const DeleteDatabaseConfigVariableDocument = gql` + mutation DeleteDatabaseConfigVariable($key: String!) { + deleteDatabaseConfigVariable(key: $key) +} + `; +export type DeleteDatabaseConfigVariableMutationFn = Apollo.MutationFunction; + +/** + * __useDeleteDatabaseConfigVariableMutation__ + * + * To run a mutation, you first call `useDeleteDatabaseConfigVariableMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteDatabaseConfigVariableMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [deleteDatabaseConfigVariableMutation, { data, loading, error }] = useDeleteDatabaseConfigVariableMutation({ + * variables: { + * key: // value for 'key' + * }, + * }); + */ +export function useDeleteDatabaseConfigVariableMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(DeleteDatabaseConfigVariableDocument, options); + } +export type DeleteDatabaseConfigVariableMutationHookResult = ReturnType; +export type DeleteDatabaseConfigVariableMutationResult = Apollo.MutationResult; +export type DeleteDatabaseConfigVariableMutationOptions = Apollo.BaseMutationOptions; +export const UpdateDatabaseConfigVariableDocument = gql` + mutation UpdateDatabaseConfigVariable($key: String!, $value: JSON!) { + updateDatabaseConfigVariable(key: $key, value: $value) +} + `; +export type UpdateDatabaseConfigVariableMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateDatabaseConfigVariableMutation__ + * + * To run a mutation, you first call `useUpdateDatabaseConfigVariableMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateDatabaseConfigVariableMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [updateDatabaseConfigVariableMutation, { data, loading, error }] = useUpdateDatabaseConfigVariableMutation({ + * variables: { + * key: // value for 'key' + * value: // value for 'value' + * }, + * }); + */ +export function useUpdateDatabaseConfigVariableMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateDatabaseConfigVariableDocument, options); + } +export type UpdateDatabaseConfigVariableMutationHookResult = ReturnType; +export type UpdateDatabaseConfigVariableMutationResult = Apollo.MutationResult; +export type UpdateDatabaseConfigVariableMutationOptions = Apollo.BaseMutationOptions; +export const GetConfigVariablesGroupedDocument = gql` + query GetConfigVariablesGrouped { + getConfigVariablesGrouped { + groups { + name + description + isHiddenOnLoad + variables { + name + description + value + isSensitive + isEnvOnly + type + options + source + } + } + } +} + `; + +/** + * __useGetConfigVariablesGroupedQuery__ + * + * To run a query within a React component, call `useGetConfigVariablesGroupedQuery` and pass it any options that fit your needs. + * When your component renders, `useGetConfigVariablesGroupedQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetConfigVariablesGroupedQuery({ + * variables: { + * }, + * }); + */ +export function useGetConfigVariablesGroupedQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetConfigVariablesGroupedDocument, options); + } +export function useGetConfigVariablesGroupedLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetConfigVariablesGroupedDocument, options); + } +export type GetConfigVariablesGroupedQueryHookResult = ReturnType; +export type GetConfigVariablesGroupedLazyQueryHookResult = ReturnType; +export type GetConfigVariablesGroupedQueryResult = Apollo.QueryResult; +export const GetDatabaseConfigVariableDocument = gql` + query GetDatabaseConfigVariable($key: String!) { + getDatabaseConfigVariable(key: $key) { + name + description + value + isSensitive + isEnvOnly + type + options + source + } +} + `; + +/** + * __useGetDatabaseConfigVariableQuery__ + * + * To run a query within a React component, call `useGetDatabaseConfigVariableQuery` and pass it any options that fit your needs. + * When your component renders, `useGetDatabaseConfigVariableQuery` returns an object from Apollo Client that contains loading, error, and data properties + * you can use to render your UI. + * + * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; + * + * @example + * const { data, loading, error } = useGetDatabaseConfigVariableQuery({ + * variables: { + * key: // value for 'key' + * }, + * }); + */ +export function useGetDatabaseConfigVariableQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetDatabaseConfigVariableDocument, options); + } +export function useGetDatabaseConfigVariableLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetDatabaseConfigVariableDocument, options); + } +export type GetDatabaseConfigVariableQueryHookResult = ReturnType; +export type GetDatabaseConfigVariableLazyQueryHookResult = ReturnType; +export type GetDatabaseConfigVariableQueryResult = Apollo.QueryResult; export const UpdateWorkspaceFeatureFlagDocument = gql` mutation UpdateWorkspaceFeatureFlag($workspaceId: String!, $featureFlag: String!, $value: Boolean!) { updateWorkspaceFeatureFlag( @@ -4714,50 +4975,6 @@ export function useUserLookupAdminPanelMutation(baseOptions?: Apollo.MutationHoo export type UserLookupAdminPanelMutationHookResult = ReturnType; export type UserLookupAdminPanelMutationResult = Apollo.MutationResult; export type UserLookupAdminPanelMutationOptions = Apollo.BaseMutationOptions; -export const GetConfigVariablesGroupedDocument = gql` - query GetConfigVariablesGrouped { - getConfigVariablesGrouped { - groups { - name - description - isHiddenOnLoad - variables { - name - description - value - isSensitive - } - } - } -} - `; - -/** - * __useGetConfigVariablesGroupedQuery__ - * - * To run a query within a React component, call `useGetConfigVariablesGroupedQuery` and pass it any options that fit your needs. - * When your component renders, `useGetConfigVariablesGroupedQuery` returns an object from Apollo Client that contains loading, error, and data properties - * you can use to render your UI. - * - * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; - * - * @example - * const { data, loading, error } = useGetConfigVariablesGroupedQuery({ - * variables: { - * }, - * }); - */ -export function useGetConfigVariablesGroupedQuery(baseOptions?: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetConfigVariablesGroupedDocument, options); - } -export function useGetConfigVariablesGroupedLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetConfigVariablesGroupedDocument, options); - } -export type GetConfigVariablesGroupedQueryHookResult = ReturnType; -export type GetConfigVariablesGroupedLazyQueryHookResult = ReturnType; -export type GetConfigVariablesGroupedQueryResult = Apollo.QueryResult; export const GetVersionInfoDocument = gql` query GetVersionInfo { versionInfo { diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 6d98a647f..dee3dfea9 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -281,11 +281,11 @@ const SettingsAdminIndicatorHealthStatus = lazy(() => })), ); -const SettingsAdminSecondaryEnvVariables = lazy(() => +const SettingsAdminConfigVariableDetails = lazy(() => import( - '~/pages/settings/admin-panel/SettingsAdminSecondaryEnvVariables' + '~/pages/settings/admin-panel/SettingsAdminConfigVariableDetails' ).then((module) => ({ - default: module.SettingsAdminSecondaryEnvVariables, + default: module.SettingsAdminConfigVariableDetails, })), ); @@ -505,9 +505,10 @@ export const SettingsRoutes = ({ path={SettingsPath.AdminPanelIndicatorHealthStatus} element={} /> + } + path={SettingsPath.AdminPanelConfigVariableDetails} + element={} /> )} diff --git a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx index 3240420e7..685499059 100644 --- a/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx +++ b/packages/twenty-front/src/modules/client-config/components/ClientConfigProviderEffect.tsx @@ -7,6 +7,7 @@ import { chromeExtensionIdState } from '@/client-config/states/chromeExtensionId import { clientConfigApiStatusState } from '@/client-config/states/clientConfigApiStatusState'; import { isAnalyticsEnabledState } from '@/client-config/states/isAnalyticsEnabledState'; import { isAttachmentPreviewEnabledState } from '@/client-config/states/isAttachmentPreviewEnabledState'; +import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState'; import { isDebugModeState } from '@/client-config/states/isDebugModeState'; import { isDeveloperDefaultSignInPrefilledState } from '@/client-config/states/isDeveloperDefaultSignInPrefilledState'; import { isEmailVerificationRequiredState } from '@/client-config/states/isEmailVerificationRequiredState'; @@ -21,8 +22,8 @@ import { supportChatState } from '@/client-config/states/supportChatState'; import { domainConfigurationState } from '@/domain-manager/states/domainConfigurationState'; import { useEffect } from 'react'; import { useRecoilState, useSetRecoilState } from 'recoil'; -import { useGetClientConfigQuery } from '~/generated/graphql'; import { isDefined } from 'twenty-shared/utils'; +import { useGetClientConfigQuery } from '~/generated/graphql'; export const ClientConfigProviderEffect = () => { const setIsDebugMode = useSetRecoilState(isDebugModeState); @@ -82,6 +83,10 @@ export const ClientConfigProviderEffect = () => { isAttachmentPreviewEnabledState, ); + const setIsConfigVariablesInDbEnabled = useSetRecoilState( + isConfigVariablesInDbEnabledState, + ); + const { data, loading, error } = useGetClientConfigQuery({ skip: clientConfigApiStatus.isLoaded, }); @@ -157,6 +162,9 @@ export const ClientConfigProviderEffect = () => { setIsAttachmentPreviewEnabled( data?.clientConfig?.isAttachmentPreviewEnabled, ); + setIsConfigVariablesInDbEnabled( + data?.clientConfig?.isConfigVariablesInDbEnabled, + ); }, [ data, setIsDebugMode, @@ -182,6 +190,7 @@ export const ClientConfigProviderEffect = () => { setGoogleMessagingEnabled, setGoogleCalendarEnabled, setIsAttachmentPreviewEnabled, + setIsConfigVariablesInDbEnabled, ]); return <>; diff --git a/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts b/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts index 949dc281c..215912cd6 100644 --- a/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts +++ b/packages/twenty-front/src/modules/client-config/graphql/queries/getClientConfig.ts @@ -61,6 +61,7 @@ export const GET_CLIENT_CONFIG = gql` isMicrosoftCalendarEnabled isGoogleMessagingEnabled isGoogleCalendarEnabled + isConfigVariablesInDbEnabled } } `; diff --git a/packages/twenty-front/src/modules/client-config/states/isConfigVariablesInDbEnabledState.ts b/packages/twenty-front/src/modules/client-config/states/isConfigVariablesInDbEnabledState.ts new file mode 100644 index 000000000..e5edda28a --- /dev/null +++ b/packages/twenty-front/src/modules/client-config/states/isConfigVariablesInDbEnabledState.ts @@ -0,0 +1,5 @@ +import { createState } from 'twenty-ui/utilities'; +export const isConfigVariablesInDbEnabledState = createState({ + key: 'isConfigVariablesInDbEnabled', + defaultValue: false, +}); diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx index 110062f2e..55c40dbca 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminContent.tsx @@ -20,8 +20,8 @@ export const SettingsAdminContent = () => { disabled: !canAccessFullAdminPanel && !canImpersonate, }, { - id: SETTINGS_ADMIN_TABS.ENV_VARIABLES, - title: t`Env Variables`, + id: SETTINGS_ADMIN_TABS.CONFIG_VARIABLES, + title: t`Config Variables`, Icon: IconVariable, disabled: !canAccessFullAdminPanel, }, diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariables.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariables.tsx deleted file mode 100644 index 7aed808d1..000000000 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariables.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { SettingsAdminEnvVariablesTable } from '@/settings/admin-panel/components/SettingsAdminEnvVariablesTable'; -import { SettingsAdminTabSkeletonLoader } from '@/settings/admin-panel/components/SettingsAdminTabSkeletonLoader'; -import { SettingsListItemCardContent } from '@/settings/components/SettingsListItemCardContent'; -import { SettingsPath } from '@/types/SettingsPath'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { t } from '@lingui/core/macro'; -import { H2Title, IconHeartRateMonitor } from 'twenty-ui/display'; -import { Card, Section } from 'twenty-ui/layout'; -import { useGetConfigVariablesGroupedQuery } from '~/generated/graphql'; -import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; - -const StyledGroupContainer = styled.div``; - -const StyledInfoText = styled.div` - color: ${({ theme }) => theme.font.color.secondary}; -`; - -const StyledCard = styled(Card)` - margin-bottom: ${({ theme }) => theme.spacing(8)}; -`; - -export const SettingsAdminEnvVariables = () => { - const theme = useTheme(); - const { data: configVariables, loading: configVariablesLoading } = - useGetConfigVariablesGroupedQuery({ - fetchPolicy: 'network-only', - }); - - const visibleGroups = - configVariables?.getConfigVariablesGrouped.groups.filter( - (group) => !group.isHiddenOnLoad, - ) ?? []; - - if (configVariablesLoading) { - return ; - } - - return ( - <> -
- - {t`These are only the server values. Ensure your worker environment has the same variables and values, this is required for asynchronous tasks like email sync.`} - -
- {visibleGroups.map((group) => ( - - - {group.variables.length > 0 && ( - - )} - - ))} - -
- - - -
- - ); -}; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariablesRow.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariablesRow.tsx deleted file mode 100644 index 27ecee163..000000000 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariablesRow.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { SettingsAdminEnvCopyableText } from '@/settings/admin-panel/components/SettingsAdminEnvCopyableText'; -import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard'; -import { TableCell } from '@/ui/layout/table/components/TableCell'; -import { TableRow } from '@/ui/layout/table/components/TableRow'; -import { useTheme } from '@emotion/react'; -import styled from '@emotion/styled'; -import { motion } from 'framer-motion'; -import { useState } from 'react'; -import { IconChevronRight, IconEye, IconEyeOff } from 'twenty-ui/display'; -import { LightIconButton } from 'twenty-ui/input'; -import { AnimatedExpandableContainer } from 'twenty-ui/layout'; - -type SettingsAdminEnvVariablesRowProps = { - variable: { - name: string; - description: string; - value: string; - isSensitive: boolean; - }; - isExpanded: boolean; - onExpandToggle: (name: string) => void; -}; - -const StyledTruncatedCell = styled(TableCell)` - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: pointer; -`; - -const StyledButton = styled(motion.button)` - align-items: center; - border: none; - display: flex; - justify-content: center; - padding-inline: ${({ theme }) => theme.spacing(1)}; - background-color: transparent; - height: 24px; - width: 24px; - box-sizing: border-box; - cursor: pointer; -`; - -const MotionIconChevronDown = motion(IconChevronRight); - -const StyledEllipsisLabel = styled.div` - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -`; - -const StyledValueContainer = styled.div` - align-items: center; - display: flex; - gap: ${({ theme }) => theme.spacing(1)}; - justify-content: space-between; - width: 100%; -`; - -const StyledTableRow = styled(TableRow)<{ isExpanded: boolean }>` - background-color: ${({ isExpanded, theme }) => - isExpanded ? theme.background.transparent.light : 'transparent'}; -`; - -const StyledExpandableContainer = styled.div` - width: 100%; - padding-top: ${({ theme }) => theme.spacing(2)}; - padding-bottom: ${({ theme }) => theme.spacing(2)}; -`; - -export const SettingsAdminEnvVariablesRow = ({ - variable, - isExpanded, - onExpandToggle, -}: SettingsAdminEnvVariablesRowProps) => { - const [showSensitiveValue, setShowSensitiveValue] = useState(false); - const theme = useTheme(); - - const displayValue = - variable.value === '' - ? 'null' - : variable.isSensitive && !showSensitiveValue - ? '••••••' - : variable.value; - - const handleToggleVisibility = (event: React.MouseEvent) => { - event.stopPropagation(); - setShowSensitiveValue(!showSensitiveValue); - }; - - const environmentVariablesDetails = [ - { - label: 'Name', - value: , - }, - { - label: 'Description', - value: ( - - ), - }, - { - label: 'Value', - value: ( - - - {variable.isSensitive && variable.value !== '' && ( - - )} - - ), - }, - ]; - - return ( - <> - onExpandToggle(variable.name)} - gridAutoColumns="5fr 4fr 3fr 1fr" - isExpanded={isExpanded} - > - - {variable.name} - - - {variable.description} - - - {displayValue} - - - { - e.stopPropagation(); - onExpandToggle(variable.name); - }} - > - - - - - - - - - - - ); -}; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariablesTable.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariablesTable.tsx deleted file mode 100644 index e40b58cee..000000000 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminEnvVariablesTable.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { SettingsAdminEnvVariablesRow } from '@/settings/admin-panel/components/SettingsAdminEnvVariablesRow'; -import { Table } from '@/ui/layout/table/components/Table'; -import { TableBody } from '@/ui/layout/table/components/TableBody'; -import { TableHeader } from '@/ui/layout/table/components/TableHeader'; -import { TableRow } from '@/ui/layout/table/components/TableRow'; -import styled from '@emotion/styled'; -import { useState } from 'react'; - -const StyledTableBody = styled(TableBody)` - border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; -`; - -type SettingsAdminEnvVariablesTableProps = { - variables: Array<{ - name: string; - description: string; - value: string; - isSensitive: boolean; - }>; -}; - -export const SettingsAdminEnvVariablesTable = ({ - variables, -}: SettingsAdminEnvVariablesTableProps) => { - const [expandedRowName, setExpandedRowName] = useState(null); - - const handleExpandToggle = (name: string) => { - setExpandedRowName(expandedRowName === name ? null : name); - }; - - return ( - - - Name - Description - Value - - - - {variables.map((variable) => ( - - ))} - -
- ); -}; diff --git a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx index 934b702c9..9fa8a32b7 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/components/SettingsAdminTabContent.tsx @@ -1,5 +1,5 @@ -import { SettingsAdminEnvVariables } from '@/settings/admin-panel/components/SettingsAdminEnvVariables'; import { SettingsAdminGeneral } from '@/settings/admin-panel/components/SettingsAdminGeneral'; +import { SettingsAdminConfigVariables } from '@/settings/admin-panel/config-variables/components/SettingsAdminConfigVariables'; import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs'; import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId'; import { SettingsAdminHealthStatus } from '@/settings/admin-panel/health-status/components/SettingsAdminHealthStatus'; @@ -15,8 +15,8 @@ export const SettingsAdminTabContent = () => { switch (activeTabId) { case SETTINGS_ADMIN_TABS.GENERAL: return ; - case SETTINGS_ADMIN_TABS.ENV_VARIABLES: - return ; + case SETTINGS_ADMIN_TABS.CONFIG_VARIABLES: + return ; case SETTINGS_ADMIN_TABS.HEALTH_STATUS: return ; default: diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableActionButtons.tsx b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableActionButtons.tsx new file mode 100644 index 000000000..8a87c7bd9 --- /dev/null +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableActionButtons.tsx @@ -0,0 +1,62 @@ +import { useLingui } from '@lingui/react/macro'; +import { useRecoilValue } from 'recoil'; + +import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState'; +import { + IconDeviceFloppy, + IconPencil, + IconRefreshAlert, +} from 'twenty-ui/display'; +import { Button } from 'twenty-ui/input'; +import { ConfigSource, ConfigVariable } from '~/generated/graphql'; + +type ConfigVariableActionButtonsProps = { + variable: ConfigVariable; + isValueValid: boolean; + isSubmitting: boolean; + onSave: () => void; + onReset: () => void; +}; + +export const ConfigVariableActionButtons = ({ + variable, + isValueValid, + isSubmitting, + onSave, + onReset, +}: ConfigVariableActionButtonsProps) => { + const { t } = useLingui(); + const isConfigVariablesInDbEnabled = useRecoilValue( + isConfigVariablesInDbEnabledState, + ); + const isFromDatabase = variable.source === ConfigSource.DATABASE; + + return ( + <> + {isConfigVariablesInDbEnabled && + variable.source === ConfigSource.DATABASE && ( +