fix(front): keep app variable cache in sync after update (#20861)

Updating an application variable in Workspace / Applications / <App> /
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 <martmull@hotmail.fr>
This commit is contained in:
Nicolas Besnard
2026-06-01 10:14:28 +02:00
committed by GitHub
parent 26906951b3
commit 71c377484e
2 changed files with 126 additions and 2 deletions
@@ -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 }) => (
<MockedProvider mocks={mocks} cache={cache}>
{children}
</MockedProvider>
);
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);
});
});
});
@@ -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 };