From 9fb4792feb49cece37360d22f314c60574a6c349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20D=C3=ADaz=20Gonz=C3=A1lez?= Date: Sat, 7 Jun 2025 10:03:19 +0100 Subject: [PATCH] refactor(web): use query key factories to avoid typos and improve consistency Instead of writing query keys manually each time, this commit introduces the use of query key factories. This helps prevent mistakes like typos or mismatched keys, ensures consistency, and improves maintainability by centralizing key definitions, making future changes easier. This follows a recommended pattern from the React Query community: https://tkdodo.eu/blog/effective-react-query-keys#use-query-key-factories --- web/src/queries/issues.ts | 9 ++- web/src/queries/l10n.ts | 18 ++++-- web/src/queries/network.ts | 43 ++++++++------ web/src/queries/progress.ts | 13 ++-- web/src/queries/questions.ts | 8 ++- web/src/queries/software.ts | 79 +++++++++++++++---------- web/src/queries/storage.ts | 62 +++++++++++++------ web/src/queries/storage/config-model.ts | 4 +- web/src/queries/storage/dasd.ts | 40 ++++++++----- web/src/queries/storage/iscsi.ts | 35 +++++++---- web/src/queries/storage/zfcp.ts | 19 ++++-- web/src/queries/system.ts | 9 ++- web/src/queries/users.ts | 28 +++++---- 13 files changed, 241 insertions(+), 126 deletions(-) diff --git a/web/src/queries/issues.ts b/web/src/queries/issues.ts index 41b5888f63..55fbcd0cae 100644 --- a/web/src/queries/issues.ts +++ b/web/src/queries/issues.ts @@ -33,9 +33,14 @@ const scopesFromPath = { "/org/opensuse/Agama/Users1": "users", }; +const issuesKeys = { + all: () => ["issues"] as const, + byScope: (scope: IssuesScope) => [...issuesKeys.all(), scope] as const, +}; + const issuesQuery = (scope: IssuesScope) => { return { - queryKey: ["issues", scope], + queryKey: issuesKeys.byScope(scope), queryFn: () => fetchIssues(scope), }; }; @@ -77,7 +82,7 @@ const useIssuesChanges = () => { const scope = scopesFromPath[path]; // TODO: use setQueryData because all the issues are included in the event if (scope) { - queryClient.invalidateQueries({ queryKey: ["issues", scope] }); + queryClient.invalidateQueries({ queryKey: issuesKeys.byScope(scope) }); } else { console.warn(`Unknown scope ${path}`); } diff --git a/web/src/queries/l10n.ts b/web/src/queries/l10n.ts index 4e48ada107..3071841d9f 100644 --- a/web/src/queries/l10n.ts +++ b/web/src/queries/l10n.ts @@ -25,12 +25,20 @@ import { useQueryClient, useMutation, useSuspenseQueries } from "@tanstack/react import { useInstallerClient } from "~/context/installer"; import { fetchConfig, fetchKeymaps, fetchLocales, fetchTimezones, updateConfig } from "~/api/l10n"; +const l10nKeys = { + all: () => ["l10n"] as const, + config: () => [...l10nKeys.all(), "config"] as const, + locales: () => [...l10nKeys.all(), "locales"] as const, + keymaps: () => [...l10nKeys.all(), "keymaps"] as const, + timezones: () => [...l10nKeys.all(), "timezones"] as const, +}; + /** * Returns a query for retrieving the localization configuration */ const configQuery = () => { return { - queryKey: ["l10n", "config"], + queryKey: l10nKeys.config(), queryFn: fetchConfig, }; }; @@ -39,7 +47,7 @@ const configQuery = () => { * Returns a query for retrieving the list of known locales */ const localesQuery = () => ({ - queryKey: ["l10n", "locales"], + queryKey: l10nKeys.locales(), queryFn: fetchLocales, staleTime: Infinity, }); @@ -48,7 +56,7 @@ const localesQuery = () => ({ * Returns a query for retrieving the list of known timezones */ const timezonesQuery = () => ({ - queryKey: ["l10n", "timezones"], + queryKey: l10nKeys.timezones(), queryFn: fetchTimezones, staleTime: Infinity, }); @@ -57,7 +65,7 @@ const timezonesQuery = () => ({ * Returns a query for retrieving the list of known keymaps */ const keymapsQuery = () => ({ - queryKey: ["l10n", "keymaps"], + queryKey: l10nKeys.keymaps(), queryFn: fetchKeymaps, staleTime: Infinity, }); @@ -88,7 +96,7 @@ const useL10nConfigChanges = () => { return client.onEvent((event) => { if (event.type === "L10nConfigChanged") { - queryClient.invalidateQueries({ queryKey: ["l10n"] }); + queryClient.invalidateQueries({ queryKey: l10nKeys.all() }); } }); }, [client, queryClient]); diff --git a/web/src/queries/network.ts b/web/src/queries/network.ts index 09a37ee7e0..0346e4070b 100644 --- a/web/src/queries/network.ts +++ b/web/src/queries/network.ts @@ -46,12 +46,21 @@ import { updateConnection, } from "~/api/network"; +const networkKeys = { + all: () => ["network"] as const, + state: () => [...networkKeys.all(), "state"] as const, + devices: () => [...networkKeys.all(), "devices"] as const, + connections: () => [...networkKeys.all(), "connections"] as const, + connection: (name: string) => [...networkKeys.connections(), name] as const, + accessPoints: () => [...networkKeys.all(), "accessPoints"] as const, +}; + /** * Returns a query for retrieving the general network configuration */ const stateQuery = () => { return { - queryKey: ["network", "state"], + queryKey: networkKeys.state(), queryFn: fetchState, }; }; @@ -60,7 +69,7 @@ const stateQuery = () => { * Returns a query for retrieving the list of known devices */ const devicesQuery = () => ({ - queryKey: ["network", "devices"], + queryKey: networkKeys.devices(), queryFn: async () => { const devices = await fetchDevices(); return devices.map(Device.fromApi); @@ -72,7 +81,7 @@ const devicesQuery = () => ({ * Returns a query for retrieving data for the given connection name */ const connectionQuery = (name: string) => ({ - queryKey: ["network", "connections", name], + queryKey: networkKeys.connection(name), queryFn: async () => { const connection = await fetchConnection(name); return Connection.fromApi(connection); @@ -84,7 +93,7 @@ const connectionQuery = (name: string) => ({ * Returns a query for retrieving the list of known connections */ const connectionsQuery = () => ({ - queryKey: ["network", "connections"], + queryKey: networkKeys.connections(), queryFn: async () => { const connections = await fetchConnections(); return connections.map(Connection.fromApi); @@ -97,7 +106,7 @@ const connectionsQuery = () => ({ * the signal strength. */ const accessPointsQuery = () => ({ - queryKey: ["network", "accessPoints"], + queryKey: networkKeys.accessPoints(), queryFn: async (): Promise => { const accessPoints = await fetchAccessPoints(); return accessPoints.map(AccessPoint.fromApi).sort((a, b) => b.strength - a.strength); @@ -117,9 +126,9 @@ const useAddConnectionMutation = () => { mutationFn: (newConnection: Connection) => addConnection(newConnection.toApi()).then(() => applyChanges()), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["network", "connections"] }); - queryClient.invalidateQueries({ queryKey: ["network", "devices"] }); - queryClient.invalidateQueries({ queryKey: ["network", "accessPoints"] }); + queryClient.invalidateQueries({ queryKey: networkKeys.connections() }); + queryClient.invalidateQueries({ queryKey: networkKeys.devices() }); + queryClient.invalidateQueries({ queryKey: networkKeys.accessPoints() }); }, }; return useMutation(query); @@ -136,8 +145,8 @@ const useConnectionMutation = () => { mutationFn: (newConnection: Connection) => updateConnection(newConnection.toApi()).then(() => applyChanges()), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["network", "connections"] }); - queryClient.invalidateQueries({ queryKey: ["network", "devices"] }); + queryClient.invalidateQueries({ queryKey: networkKeys.connections() }); + queryClient.invalidateQueries({ queryKey: networkKeys.devices() }); }, }; return useMutation(query); @@ -173,7 +182,7 @@ const useConnectionPersistMutation = () => { }); // Update the cached data with the optimistically updated connections - queryClient.setQueryData(["network", "connections"], updatedConnections); + queryClient.setQueryData(networkKeys.connections(), updatedConnections); // Return the previous state for potential rollback return { previousConnections }; @@ -203,8 +212,8 @@ const useRemoveConnectionMutation = () => { .then(() => applyChanges()) .catch((e) => console.log(e)), onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["network", "connections"] }); - queryClient.invalidateQueries({ queryKey: ["network", "devices"] }); + queryClient.invalidateQueries({ queryKey: networkKeys.connections() }); + queryClient.invalidateQueries({ queryKey: networkKeys.devices() }); }, }; return useMutation(query); @@ -222,18 +231,18 @@ const useNetworkChanges = () => { const updateDevices = useCallback( (func: (devices: Device[]) => Device[]) => { - const devices: Device[] = queryClient.getQueryData(["network", "devices"]); + const devices: Device[] = queryClient.getQueryData(networkKeys.devices()); if (!devices) return; const updatedDevices = func(devices); - queryClient.setQueryData(["network", "devices"], updatedDevices); + queryClient.setQueryData(networkKeys.devices(), updatedDevices); }, [queryClient], ); const updateConnectionState = useCallback( (id: string, state: string) => { - const connections: Connection[] = queryClient.getQueryData(["network", "connections"]); + const connections: Connection[] = queryClient.getQueryData(networkKeys.connections()); if (!connections) return; const updatedConnections = connections.map((conn) => { @@ -244,7 +253,7 @@ const useNetworkChanges = () => { } return conn; }); - queryClient.setQueryData(["network", "connections"], updatedConnections); + queryClient.setQueryData(networkKeys.connections(), updatedConnections); }, [queryClient], ); diff --git a/web/src/queries/progress.ts b/web/src/queries/progress.ts index 00a582e3a5..8a5da2d008 100644 --- a/web/src/queries/progress.ts +++ b/web/src/queries/progress.ts @@ -33,6 +33,11 @@ const servicesMap = { "/org/opensuse/Agama/Storage1": "storage", }; +const progressKeys = { + all: () => ["progress"] as const, + byService: (service: string) => [...progressKeys.all(), service] as const, +}; + /** * Returns a query for retrieving the progress information for a given service * @@ -43,7 +48,7 @@ const servicesMap = { */ const progressQuery = (service: string) => { return { - queryKey: ["progress", service], + queryKey: progressKeys.byService(service), queryFn: () => fetchProgress(service), }; }; @@ -83,12 +88,12 @@ const useProgressChanges = () => { return; } - const data = queryClient.getQueryData(["progress", service]); + const data = queryClient.getQueryData(progressKeys.byService(service)); if (data) { // NOTE: steps are not coming in the updates const steps = (data as Progress).steps; const fromEvent = Progress.fromApi(event); - queryClient.setQueryData(["progress", service], { ...fromEvent, steps }); + queryClient.setQueryData(progressKeys.byService(service), { ...fromEvent, steps }); } } }); @@ -106,7 +111,7 @@ const useResetProgress = () => { React.useEffect(() => { return () => { - queryClient.invalidateQueries({ queryKey: ["progress"] }); + queryClient.invalidateQueries({ queryKey: progressKeys.all() }); }; }, [queryClient]); }; diff --git a/web/src/queries/questions.ts b/web/src/queries/questions.ts index 2615e1b7e7..5e8046aa38 100644 --- a/web/src/queries/questions.ts +++ b/web/src/queries/questions.ts @@ -26,6 +26,10 @@ import { useInstallerClient } from "~/context/installer"; import { Question } from "~/types/questions"; import { fetchQuestions, updateAnswer } from "~/api/questions"; +const questionsKeys = { + all: () => ["questions"] as const, +}; + /** * Query to retrieve questions */ @@ -60,7 +64,7 @@ const useQuestionsChanges = () => { return client.onEvent((event) => { if (event.type === "QuestionsChanged") { - queryClient.invalidateQueries({ queryKey: ["questions"] }); + queryClient.invalidateQueries({ queryKey: questionsKeys.all() }); } }); }, [client, queryClient]); @@ -69,7 +73,7 @@ const useQuestionsChanges = () => { if (!client) return; return client.onConnect(() => { - queryClient.invalidateQueries({ queryKey: ["questions"] }); + queryClient.invalidateQueries({ queryKey: questionsKeys.all() }); }); }, [client, queryClient]); }; diff --git a/web/src/queries/software.ts b/web/src/queries/software.ts index d21c9c8d4a..e36d072328 100644 --- a/web/src/queries/software.ts +++ b/web/src/queries/software.ts @@ -64,12 +64,28 @@ import { } from "~/api/software"; import { QueryHookOptions } from "~/types/queries"; import { probe as systemProbe, reprobe as systemReprobe } from "~/api/manager"; +import { storageKeys } from "./storage"; + +const softwareKeys = { + all: () => ["software"] as const, + config: () => [...softwareKeys.all(), "config"] as const, + proposal: () => [...softwareKeys.all(), "proposal"] as const, + products: () => [...softwareKeys.all(), "products"] as const, + licenses: () => [...softwareKeys.all(), "licenses"] as const, + selectedProduct: () => [...softwareKeys.all(), "product"] as const, + registration: () => [...softwareKeys.all(), "registration"] as const, + addons: () => [...softwareKeys.registration(), "addons"] as const, + registeredAddons: () => [...softwareKeys.addons(), "registered"] as const, + patterns: () => [...softwareKeys.all(), "patterns"] as const, + repositories: () => [...softwareKeys.all(), "repositories"] as const, + conflicts: () => [...softwareKeys.all(), "conflicts"] as const, +}; /** * Query to retrieve software configuration */ const configQuery = () => ({ - queryKey: ["software", "config"], + queryKey: softwareKeys.config(), queryFn: fetchConfig, }); @@ -77,24 +93,15 @@ const configQuery = () => ({ * Query to retrieve current software proposal */ const proposalQuery = () => ({ - queryKey: ["software", "proposal"], + queryKey: softwareKeys.proposal(), queryFn: fetchProposal, }); -/** - * Query to retrieve selected product - */ -const selectedProductQuery = () => ({ - queryKey: ["software", "selectedProduct"], - queryFn: () => fetchConfig().then(({ product }) => product), - staleTime: Infinity, -}); - /** * Query to retrieve available products */ const productsQuery = () => ({ - queryKey: ["software", "products"], + queryKey: softwareKeys.products(), queryFn: fetchProducts, staleTime: Infinity, }); @@ -103,16 +110,24 @@ const productsQuery = () => ({ * Query to retrieve available licenses */ const licensesQuery = () => ({ - queryKey: ["software", "licenses"], + queryKey: softwareKeys.licenses(), queryFn: fetchLicenses, staleTime: Infinity, }); +/** + * Query to retrieve selected product + */ +const selectedProductQuery = () => ({ + queryKey: softwareKeys.selectedProduct(), + queryFn: () => fetchConfig().then(({ product }) => product), +}); + /** * Query to retrieve registration info */ const registrationQuery = () => ({ - queryKey: ["software", "registration"], + queryKey: softwareKeys.registration(), queryFn: fetchRegistration, }); @@ -120,7 +135,7 @@ const registrationQuery = () => ({ * Query to retrieve available addons info */ const addonsQuery = () => ({ - queryKey: ["software", "registration", "addons"], + queryKey: softwareKeys.addons(), queryFn: fetchAddons, }); @@ -128,7 +143,7 @@ const addonsQuery = () => ({ * Query to retrieve registered addons info */ const registeredAddonsQuery = () => ({ - queryKey: ["software", "registration", "addons", "registered"], + queryKey: softwareKeys.registeredAddons(), queryFn: fetchRegisteredAddons, }); @@ -136,7 +151,7 @@ const registeredAddonsQuery = () => ({ * Query to retrieve available patterns */ const patternsQuery = () => ({ - queryKey: ["software", "patterns"], + queryKey: softwareKeys.patterns(), queryFn: fetchPatterns, }); @@ -144,7 +159,7 @@ const patternsQuery = () => ({ * Query to retrieve configured repositories */ const repositoriesQuery = () => ({ - queryKey: ["software", "repositories"], + queryKey: softwareKeys.repositories(), queryFn: fetchRepositories, }); @@ -152,7 +167,7 @@ const repositoriesQuery = () => ({ * Query to retrieve conflicts */ const conflictsQuery = () => ({ - queryKey: ["software", "conflicts"], + queryKey: softwareKeys.conflicts(), queryFn: fetchConflicts, }); @@ -168,12 +183,12 @@ const useConfigMutation = () => { const query = { mutationFn: updateConfig, onSuccess: async (_, config: SoftwareConfig) => { - queryClient.invalidateQueries({ queryKey: ["software", "config"] }); - queryClient.invalidateQueries({ queryKey: ["software", "proposal"] }); + queryClient.invalidateQueries({ queryKey: softwareKeys.config() }); + queryClient.invalidateQueries({ queryKey: softwareKeys.proposal() }); if (config.product) { - queryClient.invalidateQueries({ queryKey: ["software", "selectedProduct"] }); + queryClient.invalidateQueries({ queryKey: softwareKeys.selectedProduct() }); await systemProbe(); - queryClient.invalidateQueries({ queryKey: ["storage"] }); + queryClient.invalidateQueries({ queryKey: storageKeys.all() }); } }, }; @@ -194,11 +209,11 @@ const useRegisterMutation = () => { await updateRegistrationUrl(url).then(() => register({ key, email })); }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: ["software", "registration"] }); + queryClient.invalidateQueries({ queryKey: softwareKeys.registration() }); }, onSuccess: async () => { await systemReprobe(); - queryClient.invalidateQueries({ queryKey: ["storage"] }); + queryClient.invalidateQueries({ queryKey: storageKeys.all() }); }, }; return useMutation(query); @@ -214,7 +229,7 @@ const useRegisterAddonMutation = () => { const query = { mutationFn: registerAddon, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: registeredAddonsQuery().queryKey }); + queryClient.invalidateQueries({ queryKey: softwareKeys.registeredAddons() }); }, }; return useMutation(query); @@ -229,7 +244,7 @@ const useRepositoryMutation = (callback: () => void) => { const query = { mutationFn: probe, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["software", "repositories"] }); + queryClient.invalidateQueries({ queryKey: softwareKeys.repositories() }); callback(); }, }; @@ -357,7 +372,7 @@ const useConflictsMutation = () => { const query = { mutationFn: solveConflict, onSuccess: async () => { - queryClient.invalidateQueries({ queryKey: conflictsQuery().queryKey }); + queryClient.invalidateQueries({ queryKey: softwareKeys.conflicts() }); }, }; return useMutation(query); @@ -377,11 +392,11 @@ const useProductChanges = () => { return client.onEvent((event) => { if (event.type === "ProductChanged") { - queryClient.invalidateQueries({ queryKey: ["software"] }); + queryClient.invalidateQueries({ queryKey: softwareKeys.all() }); } if (event.type === "LocaleChanged") { - queryClient.invalidateQueries({ queryKey: ["software", "products"] }); + queryClient.invalidateQueries({ queryKey: softwareKeys.products() }); } }); }, [client, queryClient]); @@ -401,7 +416,7 @@ const useProposalChanges = () => { return client.onEvent((event) => { if (event.type === "SoftwareProposalChanged") { - queryClient.invalidateQueries({ queryKey: ["software", "proposal"] }); + queryClient.invalidateQueries({ queryKey: softwareKeys.proposal() }); } }); }, [client, queryClient]); @@ -420,7 +435,7 @@ const useConflictsChanges = () => { return client.onEvent((event) => { if (event.type === "ConflictsChanged") { const { conflicts } = event; - queryClient.setQueryData([conflictsQuery().queryKey], conflicts); + queryClient.setQueryData(softwareKeys.conflicts(), conflicts); } }); }); diff --git a/web/src/queries/storage.ts b/web/src/queries/storage.ts index 27683f431c..2edb9e5182 100644 --- a/web/src/queries/storage.ts +++ b/web/src/queries/storage.ts @@ -44,79 +44,104 @@ import { config, apiModel, ProductParams, Volume } from "~/api/storage/types"; import { Action, StorageDevice } from "~/types/storage"; import { QueryHookOptions } from "~/types/queries"; +type DevicesScope = "result" | "system"; + +const storageKeys = { + all: () => ["storage"] as const, + deprecated: () => [...storageKeys.all(), "dirty"] as const, + config: () => [...storageKeys.all(), "config"] as const, + apiModel: () => [...storageKeys.all(), "apiModel"] as const, + // FIXME: should it be under "apiModel" cache? + solvedApiModel: (apiModel?: apiModel.Config) => + [...storageKeys.all(), "solvedApiModel", JSON.stringify(apiModel)] as const, + devices: () => [...storageKeys.all(), "devices"] as const, + devicesActions: () => [...storageKeys.all(), "devices", "actions"] as const, + devicesByScope: (scope: DevicesScope) => [...storageKeys.all(), "devices", scope] as const, + productParams: () => [...storageKeys.all(), "productParams"] as const, + volumes: () => [...storageKeys.all(), "volumes"] as const, + // FIXME: should it under "volumes" cache instead? + volume: (mountPath: string) => [...storageKeys.all(), "volume", mountPath] as const, + // FIXME: should be them under "drives" cache? + availableDrives: () => [...storageKeys.all(), "availableDrives"] as const, + candidateDrives: () => [...storageKeys.all(), "candidateDrives"] as const, + // FIXME: should be them under "raids" cache? + availableMdRaids: () => [...storageKeys.all(), "availableMdRaids"] as const, + candidateMdRaids: () => [...storageKeys.all(), "candidateMdRaids"] as const, +}; + const configQuery = { - queryKey: ["storage", "config"], + queryKey: storageKeys.config(), queryFn: fetchConfig, staleTime: Infinity, }; const apiModelQuery = { - queryKey: ["storage", "apiModel"], + queryKey: storageKeys.apiModel(), queryFn: fetchConfigModel, staleTime: Infinity, }; const solveApiModelQuery = (apiModel?: apiModel.Config) => ({ - queryKey: ["storage", "solveApiModel", JSON.stringify(apiModel)], + queryKey: storageKeys.solvedApiModel(apiModel), queryFn: () => (apiModel ? solveConfigModel(apiModel) : Promise.resolve(null)), staleTime: Infinity, }); -const devicesQuery = (scope: "result" | "system") => ({ - queryKey: ["storage", "devices", scope], +const devicesQuery = (scope: DevicesScope) => ({ + queryKey: storageKeys.devicesByScope(scope), queryFn: () => fetchDevices(scope), staleTime: Infinity, }); const availableDrivesQuery = () => ({ - queryKey: ["storage", "availableDrives"], + queryKey: storageKeys.availableDrives(), queryFn: fetchAvailableDrives, staleTime: Infinity, }); const candidateDrivesQuery = () => ({ - queryKey: ["storage", "candidateDrives"], + queryKey: storageKeys.candidateDrives(), queryFn: fetchCandidateDrives, staleTime: Infinity, }); const availableMdRaidsQuery = () => ({ - queryKey: ["storage", "availableMdRaids"], + queryKey: storageKeys.availableMdRaids(), queryFn: fetchAvailableMdRaids, staleTime: Infinity, }); const candidateMdRaidsQuery = () => ({ - queryKey: ["storage", "candidateMdRaids"], + queryKey: storageKeys.candidateMdRaids(), queryFn: fetchCandidateMdRaids, staleTime: Infinity, }); const productParamsQuery = { - queryKey: ["storage", "productParams"], + queryKey: storageKeys.productParams(), queryFn: fetchProductParams, staleTime: Infinity, }; const volumeQuery = (mountPath: string) => ({ - queryKey: ["storage", "volume", mountPath], + queryKey: storageKeys.volume(mountPath), queryFn: () => fetchVolume(mountPath), staleTime: Infinity, }); const volumesQuery = (mountPaths: string[]) => ({ - queryKey: ["storage", "volumes"], + queryKey: storageKeys.volumes(), queryFn: () => fetchVolumes(mountPaths), staleTime: Infinity, }); const actionsQuery = { - queryKey: ["storage", "devices", "actions"], + queryKey: storageKeys.devicesActions(), queryFn: fetchActions, }; const deprecatedQuery = { - queryKey: ["storage", "dirty"], + queryKey: storageKeys.deprecated(), queryFn: fetchDevicesDirty, }; @@ -137,7 +162,7 @@ const useConfigMutation = () => { const queryClient = useQueryClient(); const query = { mutationFn: async (config: config.Config) => await setConfig(config), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["storage"] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: storageKeys.all() }), }; return useMutation(query); @@ -150,7 +175,7 @@ const useResetConfigMutation = () => { const queryClient = useQueryClient(); const query = { mutationFn: async () => await resetConfig(), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["storage"] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: storageKeys.all() }), }; return useMutation(query); @@ -255,7 +280,7 @@ const useDeprecatedChanges = () => { return client.onEvent(({ type, dirty: value }) => { if (type === "DevicesDirty") { - queryClient.setQueryData(deprecatedQuery.queryKey, value); + queryClient.setQueryData(storageKeys.deprecated(), value); } }); }); @@ -270,13 +295,14 @@ const useReprobeMutation = () => { mutationFn: async () => { await reprobe(); }, - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["storage"] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: storageKeys.all() }), }; return useMutation(query); }; export { + storageKeys, productParamsQuery, apiModelQuery, availableDrivesQuery, diff --git a/web/src/queries/storage/config-model.ts b/web/src/queries/storage/config-model.ts index 610e2cafd9..1ee341f7c1 100644 --- a/web/src/queries/storage/config-model.ts +++ b/web/src/queries/storage/config-model.ts @@ -26,7 +26,7 @@ import { useMutation, useQuery, useQueryClient, useSuspenseQuery } from "@tansta import { setConfigModel, solveConfigModel } from "~/api/storage"; import { apiModel, Volume } from "~/api/storage/types"; import { QueryHookOptions } from "~/types/queries"; -import { apiModelQuery, useVolumes } from "~/queries/storage"; +import { apiModelQuery, storageKeys, useVolumes } from "~/queries/storage"; function copyModel(model: apiModel.Config): apiModel.Config { return JSON.parse(JSON.stringify(model)); @@ -124,7 +124,7 @@ export function useConfigModelMutation() { const queryClient = useQueryClient(); const query = { mutationFn: (model: apiModel.Config) => setConfigModel(model), - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["storage"] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: storageKeys.all() }), }; return useMutation(query); diff --git a/web/src/queries/storage/dasd.ts b/web/src/queries/storage/dasd.ts index c75c2432f6..df012e1e50 100644 --- a/web/src/queries/storage/dasd.ts +++ b/web/src/queries/storage/dasd.ts @@ -36,11 +36,22 @@ import { hex } from "~/utils"; import { DASDDevice, FormatJob } from "~/types/dasd"; import { fetchStorageJobs } from "~/api/storage"; +// FIXME: should them be under "storage" cache? +const storageDasdKeys = { + all: () => ["dasd"] as const, + devices: () => [...storageDasdKeys.all(), "devices"] as const, + supported: () => [...storageDasdKeys.all(), "supported"] as const, + formatJobs: () => [...storageDasdKeys.all(), "formatJobs"] as const, + // FIXME: what is data? + formatJob: (data: string) => [...storageDasdKeys.formatJobs(), data] as const, + runningFormatJobs: () => [...storageDasdKeys.formatJobs(), "running"] as const, +}; + /** * Returns a query for retrieving the dasd devices */ const dasdDevicesQuery = () => ({ - queryKey: ["dasd", "devices"], + queryKey: storageDasdKeys.devices(), queryFn: fetchDASDDevices, }); @@ -53,7 +64,7 @@ const useDASDDevices = () => { }; const dasdSupportedQuery = { - queryKey: ["dasd", "supported"], + queryKey: storageDasdKeys.supported(), queryFn: supportedDASD, }; @@ -69,7 +80,7 @@ const useDASDSupported = (): boolean => { * Returns a query for retrieving the running dasd format jobs */ const dasdRunningFormatJobsQuery = () => ({ - queryKey: ["dasd", "formatJobs", "running"], + queryKey: storageDasdKeys.runningFormatJobs(), queryFn: () => fetchStorageJobs().then((jobs) => jobs.filter((j) => j.running).map(({ id }) => ({ jobId: id })), @@ -99,7 +110,7 @@ const useDASDFormatJobChanges = () => { // TODO: for simplicity we now just invalidate query instead of manually adding, removing or changing devices switch (event.type) { case "DASDFormatJobChanged": { - const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + const data = queryClient.getQueryData(storageDasdKeys.runningFormatJobs()) as FormatJob[]; const nextData = data.map((job) => { if (job.jobId !== event.jobId) return job; @@ -108,23 +119,23 @@ const useDASDFormatJobChanges = () => { summary: { ...job?.summary, ...event.summary }, }; }); - queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); + queryClient.setQueryData(storageDasdKeys.runningFormatJobs(), nextData); break; } case "JobAdded": { const formatJob: FormatJob = { jobId: event.job.id }; - const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + const data = queryClient.getQueryData(storageDasdKeys.runningFormatJobs()) as FormatJob[]; - queryClient.setQueryData(["dasd", "formatJobs", "running"], [...data, formatJob]); + queryClient.setQueryData(storageDasdKeys.runningFormatJobs(), [...data, formatJob]); break; } case "JobChanged": { const { id, running } = event.job; if (running) return; - const data = queryClient.getQueryData(["dasd", "formatJobs", "running"]) as FormatJob[]; + const data = queryClient.getQueryData(storageDasdKeys.runningFormatJobs()) as FormatJob[]; const nextData = data.filter((j) => j.jobId !== id); if (data.length !== nextData.length) { - queryClient.setQueryData(["dasd", "formatJobs", "running"], nextData); + queryClient.setQueryData(storageDasdKeys.runningFormatJobs(), nextData); } break; } @@ -150,7 +161,7 @@ const useDASDDevicesChanges = () => { switch (event.type) { case "DASDDeviceAdded": { const device: DASDDevice = event.device; - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + queryClient.setQueryData(storageDasdKeys.devices(), (prev: DASDDevice[]) => { return [...prev, device]; }); break; @@ -158,7 +169,7 @@ const useDASDDevicesChanges = () => { case "DASDDeviceRemoved": { const device: DASDDevice = event.device; const { id } = device; - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + queryClient.setQueryData(storageDasdKeys.devices(), (prev: DASDDevice[]) => { const res = prev.filter((dev) => dev.id !== id); return res; }); @@ -167,7 +178,7 @@ const useDASDDevicesChanges = () => { case "DASDDeviceChanged": { const device: DASDDevice = event.device; const { id } = device; - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + queryClient.setQueryData(storageDasdKeys.devices(), (prev: DASDDevice[]) => { // deep copy of original to have it immutable const res = [...prev]; const index = res.findIndex((dev) => dev.id === id); @@ -211,7 +222,7 @@ const useDASDMutation = () => { } }, onSuccess: (_: object, { action, devices }: { action: string; devices: string[] }) => { - queryClient.setQueryData(["dasd", "devices"], (prev: DASDDevice[]) => { + queryClient.setQueryData(storageDasdKeys.devices(), (prev: DASDDevice[]) => { const nextData = prev.map((prevDev) => { const dev = { ...prevDev }; if (devices.includes(dev.id)) { @@ -250,8 +261,9 @@ const useFormatDASDMutation = () => { const queryClient = useQueryClient(); const query = { mutationFn: formatDASD, + // FIXME: what is data? onSuccess: (data: string) => { - queryClient.setQueryData(["dasd", "formatJob", data], { jobId: data }); + queryClient.setQueryData(storageDasdKeys.formatJob(data), { jobId: data }); }, }; diff --git a/web/src/queries/storage/iscsi.ts b/web/src/queries/storage/iscsi.ts index a1bc2c188d..15fe6a5eb7 100644 --- a/web/src/queries/storage/iscsi.ts +++ b/web/src/queries/storage/iscsi.ts @@ -26,9 +26,17 @@ import { fetchInitiator, fetchNodes, updateInitiator } from "~/api/storage/iscsi import { ISCSIInitiator } from "~/types/storage"; import { useInstallerClient } from "~/context/installer"; import { ISCSINode } from "~/api/storage/types"; +import { storageKeys } from "../storage"; + +// FIXME: move this to sotaraKeys factory? +const storageIscsiKeys = { + all: () => ["storage", "iscsi"] as const, + initiator: () => [...storageKeys.all(), "initiator"] as const, + nodes: () => [...storageKeys.all(), "nodes"] as const, +}; const initiatorQuery = { - queryKey: ["storage", "iscsi", "initiator"], + queryKey: storageIscsiKeys.initiator(), queryFn: async (): Promise => { const initiator = await fetchInitiator(); return initiator; @@ -52,7 +60,7 @@ const useInitiatorMutation = () => { const queryClient = useQueryClient(); const query = { mutationFn: ({ name }) => updateInitiator({ name }), - onSuccess: () => queryClient.invalidateQueries({ queryKey: initiatorQuery.queryKey }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: storageIscsiKeys.initiator() }), }; return useMutation(query); }; @@ -69,20 +77,23 @@ const useInitiatorChanges = () => { return client.onEvent(({ type, name, ibft }) => { if (type !== "ISCSIInitiatorChanged") return; - queryClient.setQueryData(initiatorQuery.queryKey, (oldData: ISCSIInitiator | undefined) => { - if (oldData === undefined) return; - - return { - name: name === null ? oldData.name : name, - ibft: ibft === null ? oldData.ibft : ibft, - }; - }); + queryClient.setQueryData( + storageIscsiKeys.initiator(), + (oldData: ISCSIInitiator | undefined) => { + if (oldData === undefined) return; + + return { + name: name === null ? oldData.name : name, + ibft: ibft === null ? oldData.ibft : ibft, + }; + }, + ); }); }, [client, queryClient]); }; const nodesQuery = { - queryKey: ["storage", "iscsi", "nodes"], + queryKey: storageIscsiKeys.nodes(), queryFn: fetchNodes, }; @@ -106,7 +117,7 @@ const useNodesChanges = () => { return; } - queryClient.setQueryData(nodesQuery.queryKey, (oldData: ISCSINode[] | undefined) => { + queryClient.setQueryData(storageIscsiKeys.nodes(), (oldData: ISCSINode[] | undefined) => { if (oldData === undefined) return; switch (type) { diff --git a/web/src/queries/storage/zfcp.ts b/web/src/queries/storage/zfcp.ts index 294fde95b8..c25fe34400 100644 --- a/web/src/queries/storage/zfcp.ts +++ b/web/src/queries/storage/zfcp.ts @@ -31,25 +31,34 @@ import { useInstallerClient } from "~/context/installer"; import React from "react"; import { ZFCPConfig, ZFCPController, ZFCPDisk } from "~/types/zfcp"; +// FIXME: move this to sotaraKeys factory? +const storageZfcpKeys = { + all: () => ["storage", "zfcp"] as const, + config: () => [...storageZfcpKeys.all(), "config"] as const, + controllers: () => [...storageZfcpKeys.all(), "controllers"] as const, + disks: () => [...storageZfcpKeys.all(), "disks"] as const, + supported: () => [...storageZfcpKeys.all(), "supported"] as const, +}; + const zfcpControllersQuery = { - queryKey: ["zfcp", "controllers"], + queryKey: storageZfcpKeys.controllers(), queryFn: fetchZFCPControllers, staleTime: Infinity, }; const zfcpDisksQuery = { - queryKey: ["zfcp", "disks"], + queryKey: storageZfcpKeys.disks(), queryFn: fetchZFCPDisks, staleTime: Infinity, }; const zfcpSupportedQuery = { - queryKey: ["zfcp", "supported"], + queryKey: storageZfcpKeys.supported(), queryFn: supportedZFCP, }; const zfcpConfigQuery = { - queryKey: ["zfcp", "config"], + queryKey: storageZfcpKeys.config(), queryFn: fetchZFCPConfig, }; @@ -137,7 +146,7 @@ const useZFCPDisksChanges = () => { if (!["ZFCPDiskAdded", "ZFCPDiskChanged", "ZFCPDiskRemoved"].includes(type)) { return; } - queryClient.setQueryData(zfcpDisksQuery.queryKey, (prev: ZFCPDisk[] | undefined) => { + queryClient.setQueryData(storageZfcpKeys.disks(), (prev: ZFCPDisk[] | undefined) => { if (prev === undefined) return; switch (type) { diff --git a/web/src/queries/system.ts b/web/src/queries/system.ts index c21e2ee28a..c275f31bce 100644 --- a/web/src/queries/system.ts +++ b/web/src/queries/system.ts @@ -23,11 +23,16 @@ import { useMutation, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; import { fetchHostname, updateHostname } from "~/api/system"; +const systemKeys = { + all: () => ["system"] as const, + hostname: () => [...systemKeys.all(), "hostname"] as const, +}; + /** * Returns a query for retrieving the hostname configuration */ const hostnameQuery = () => ({ - queryKey: ["system", "hostname"], + queryKey: systemKeys.hostname(), queryFn: fetchHostname, }); @@ -46,7 +51,7 @@ const useHostnameMutation = () => { const queryClient = useQueryClient(); const query = { mutationFn: updateHostname, - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["system", "hostname"] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: systemKeys.hostname() }), }; return useMutation(query); }; diff --git a/web/src/queries/users.ts b/web/src/queries/users.ts index a132ea1dee..4bb5d70814 100644 --- a/web/src/queries/users.ts +++ b/web/src/queries/users.ts @@ -32,11 +32,17 @@ import { updateRoot, } from "~/api/users"; +const usersKeys = { + all: () => ["users"] as const, + root: () => [...usersKeys.all(), "root"] as const, + firstUser: () => [...usersKeys.all(), "firstUser"] as const, +}; + /** * Returns a query for retrieving the first user configuration */ const firstUserQuery = () => ({ - queryKey: ["users", "firstUser"], + queryKey: usersKeys.firstUser(), queryFn: fetchFirstUser, }); @@ -55,7 +61,7 @@ const useFirstUserMutation = () => { const queryClient = useQueryClient(); const query = { mutationFn: updateFirstUser, - onSuccess: () => queryClient.invalidateQueries({ queryKey: ["users", "firstUser"] }), + onSuccess: () => queryClient.invalidateQueries({ queryKey: usersKeys.firstUser() }), }; return useMutation(query); }; @@ -65,7 +71,7 @@ const useRemoveFirstUserMutation = () => { const query = { mutationFn: removeFirstUser, onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["users", "firstUser"] }); + queryClient.invalidateQueries({ queryKey: usersKeys.firstUser() }); }, }; return useMutation(query); @@ -84,7 +90,7 @@ const useFirstUserChanges = () => { return client.onEvent((event) => { if (event.type === "FirstUserChanged") { const { fullName, userName, password, hashedPassword, autologin, data } = event; - queryClient.setQueryData(["users", "firstUser"], { + queryClient.setQueryData(usersKeys.firstUser(), { fullName, userName, password, @@ -101,7 +107,7 @@ const useFirstUserChanges = () => { * Returns a query for retrieving the root user configuration. */ const rootUserQuery = () => ({ - queryKey: ["users", "root"], + queryKey: usersKeys.root(), queryFn: fetchRoot, }); @@ -118,10 +124,10 @@ const useRootUserMutation = () => { const query = { mutationFn: updateRoot, onMutate: async (newRoot: RootUser) => { - await queryClient.cancelQueries({ queryKey: ["users", "root"] }); + await queryClient.cancelQueries({ queryKey: usersKeys.root() }); - const previousRoot: RootUser = queryClient.getQueryData(["users", "root"]); - queryClient.setQueryData(["users", "root"], { + const previousRoot: RootUser = queryClient.getQueryData(usersKeys.root()); + queryClient.setQueryData(usersKeys.root(), { password: newRoot.password, hashedPassword: newRoot.hashedPassword, sshPublicKey: newRoot.sshPublicKey || previousRoot.sshPublicKey, @@ -130,10 +136,10 @@ const useRootUserMutation = () => { }, // eslint-disable-next-line n/handle-callback-err onError: (error, newRoot, context) => { - queryClient.setQueryData(["users", "root"], context.previousRoot); + queryClient.setQueryData(usersKeys.root(), context.previousRoot); }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: ["users", "root"] }); + queryClient.invalidateQueries({ queryKey: usersKeys.root() }); }, }; return useMutation(query); @@ -152,7 +158,7 @@ const useRootUserChanges = () => { return client.onEvent((event) => { if (event.type === "RootChanged") { const { password, sshPublicKey } = event; - queryClient.setQueryData(["users", "root"], (oldRoot: RootUser) => { + queryClient.setQueryData(usersKeys.root(), (oldRoot: RootUser) => { const newRoot = { ...oldRoot }; if (password !== undefined) { newRoot.password = password;