From 71c377484e4a22a6ed9371c8e64b1c7140bcbf70 Mon Sep 17 00:00:00 2001 From: Nicolas Besnard <1261670+nicolas-besnard@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:14:28 +0200 Subject: [PATCH] fix(front): keep app variable cache in sync after update (#20861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updating an application variable in Workspace / Applications / / Settings persisted server-side but the Apollo cache kept the old value. Switching tabs unmounted the settings tab, and remounting reseeded the input from the stale cache — only a full refresh showed the new value. Mutation now writes the new value into the ApplicationVariable entity via cache.modify, so FindOneApplication reflects the change immediately. Hook + table updated to pass the variable id through. Adds a hook test that pre-seeds the cache and asserts the cached value after the mutation. NOTE: saving the plain value in Apollo's cache might not be the best approach here --------- Co-authored-by: martmull --- .../useUpdateOneApplicationVariable.test.tsx | 116 ++++++++++++++++++ .../hooks/useUpdateOneApplicationVariable.ts | 12 +- 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 packages/twenty-front/src/pages/settings/applications/hooks/__tests__/useUpdateOneApplicationVariable.test.tsx diff --git a/packages/twenty-front/src/pages/settings/applications/hooks/__tests__/useUpdateOneApplicationVariable.test.tsx b/packages/twenty-front/src/pages/settings/applications/hooks/__tests__/useUpdateOneApplicationVariable.test.tsx new file mode 100644 index 0000000000..ad1e321bf1 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/applications/hooks/__tests__/useUpdateOneApplicationVariable.test.tsx @@ -0,0 +1,116 @@ +import { gql, InMemoryCache } from '@apollo/client'; +import { MockedProvider } from '@apollo/client/testing/react'; +import { act, renderHook, waitFor } from '@testing-library/react'; +import { type ReactNode } from 'react'; + +import { + type Application, + FindOneApplicationDocument, + UpdateOneApplicationVariableDocument, +} from '~/generated-metadata/graphql'; +import { useUpdateOneApplicationVariable } from '~/pages/settings/applications/hooks/useUpdateOneApplicationVariable'; + +const APPLICATION_VARIABLE_FRAGMENT = gql` + fragment TestApplicationVariableFields on ApplicationVariable { + id + key + value + description + isSecret + } +`; + +const VARIABLE_ID = 'var-1'; +const APP_ID = 'app-1'; +const KEY = 'API_KEY'; +const OLD_VALUE = 'old'; +const NEW_VALUE = 'new'; + +const buildApplication = (variableValue: string): Application => ({ + __typename: 'Application', + id: APP_ID, + name: 'Test App', + description: null, + logo: null, + version: '1.0.0', + universalIdentifier: 'test-app', + applicationRegistrationId: null, + applicationRegistration: null, + canBeUninstalled: true, + defaultRoleId: null, + settingsCustomTabFrontComponentId: null, + availablePackages: {}, + applicationVariables: [ + { + __typename: 'ApplicationVariable', + id: VARIABLE_ID, + key: KEY, + value: variableValue, + description: '', + isSecret: false, + }, + ], + agents: [], + frontComponents: [], + commandMenuItems: [], + objects: [], + logicFunctions: [], +}); + +describe('useUpdateOneApplicationVariable', () => { + it('updates the cached ApplicationVariable value after the mutation completes', async () => { + const cache = new InMemoryCache(); + + cache.writeQuery({ + query: FindOneApplicationDocument, + variables: { id: APP_ID }, + data: { findOneApplication: buildApplication(OLD_VALUE) }, + }); + + const mocks = [ + { + request: { + query: UpdateOneApplicationVariableDocument, + variables: { key: KEY, value: NEW_VALUE, applicationId: APP_ID }, + }, + result: { data: { updateOneApplicationVariable: true } }, + }, + { + request: { + query: FindOneApplicationDocument, + variables: { id: APP_ID }, + }, + result: { data: { findOneApplication: buildApplication(NEW_VALUE) } }, + }, + ]; + + const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + + ); + + const { result } = renderHook(() => useUpdateOneApplicationVariable(), { + wrapper, + }); + + await act(async () => { + await result.current.updateOneApplicationVariable({ + key: KEY, + value: NEW_VALUE, + applicationId: APP_ID, + }); + }); + + await waitFor(() => { + const cached = cache.readFragment<{ value: string }>({ + id: cache.identify({ + __typename: 'ApplicationVariable', + id: VARIABLE_ID, + }), + fragment: APPLICATION_VARIABLE_FRAGMENT, + }); + expect(cached?.value).toBe(NEW_VALUE); + }); + }); +}); diff --git a/packages/twenty-front/src/pages/settings/applications/hooks/useUpdateOneApplicationVariable.ts b/packages/twenty-front/src/pages/settings/applications/hooks/useUpdateOneApplicationVariable.ts index 0acbc0582f..5dc13f5317 100644 --- a/packages/twenty-front/src/pages/settings/applications/hooks/useUpdateOneApplicationVariable.ts +++ b/packages/twenty-front/src/pages/settings/applications/hooks/useUpdateOneApplicationVariable.ts @@ -1,5 +1,8 @@ import { useMutation } from '@apollo/client/react'; -import { UpdateOneApplicationVariableDocument } from '~/generated-metadata/graphql'; +import { + FindOneApplicationDocument, + UpdateOneApplicationVariableDocument, +} from '~/generated-metadata/graphql'; export const useUpdateOneApplicationVariable = () => { const [mutate] = useMutation(UpdateOneApplicationVariableDocument); @@ -13,7 +16,12 @@ export const useUpdateOneApplicationVariable = () => { value: string; applicationId: string; }) => { - return await mutate({ variables: { key, value, applicationId } }); + return await mutate({ + variables: { key, value, applicationId }, + refetchQueries: [ + { query: FindOneApplicationDocument, variables: { id: applicationId } }, + ], + }); }; return { updateOneApplicationVariable };