diff --git a/src/app/store/controller/slice.ts b/src/app/store/controller/slice.ts index 51ad8e7b3d..c6cd302af5 100644 --- a/src/app/store/controller/slice.ts +++ b/src/app/store/controller/slice.ts @@ -25,6 +25,7 @@ import { NodeActions } from "@/app/store/types/node"; import type { GenericItemMeta, StatusHandlers } from "@/app/store/utils/slice"; import { generateCommonReducers, + generateGetReducers, generateStatusHandlers, genericInitialState, updateErrors, @@ -289,49 +290,12 @@ const controllerSlice = createSlice({ state.loading = false; state.loaded = true; }, - get: { - prepare: (id: Controller[ControllerMeta.PK]) => ({ - meta: { - model: ControllerMeta.MODEL, - method: "get", - }, - payload: { - params: { [ControllerMeta.PK]: id }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getError: ( - state: ControllerState, - action: PayloadAction - ) => { - state.errors = action.payload; - state = setErrors(state, action, "get"); - state.loading = false; - state.saving = false; - }, - getStart: (state: ControllerState) => { - state.loading = true; - }, - getSuccess: (state: ControllerState, action: PayloadAction) => { - const controller = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: Controller) => - draftItem[ControllerMeta.PK] === controller[ControllerMeta.PK] - ); - if (i !== -1) { - state.items[i] = controller; - } else { - state.items.push(controller); - // Set up the statuses for this controller. - state.statuses[controller[ControllerMeta.PK]] = DEFAULT_STATUSES; - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: ControllerMeta.MODEL, + primaryKey: ControllerMeta.PK, + defaultStatuses: DEFAULT_STATUSES, + setErrors, + }), getSummaryXml: { prepare: (params: GetSummaryXmlParams) => ({ meta: { diff --git a/src/app/store/device/slice.ts b/src/app/store/device/slice.ts index 412acc27fb..4743bca41a 100644 --- a/src/app/store/device/slice.ts +++ b/src/app/store/device/slice.ts @@ -24,6 +24,7 @@ import type { import { NodeActions } from "@/app/store/types/node"; import { generateCommonReducers, + generateGetReducers, generateStatusHandlers, genericInitialState, updateErrors, @@ -225,49 +226,12 @@ const deviceSlice = createSlice({ deleteInterfaceError: statusHandlers.deleteInterface.error, deleteInterfaceStart: statusHandlers.deleteInterface.start, deleteInterfaceSuccess: statusHandlers.deleteInterface.success, - get: { - prepare: (id: Device[DeviceMeta.PK]) => ({ - meta: { - model: DeviceMeta.MODEL, - method: "get", - }, - payload: { - params: { [DeviceMeta.PK]: id }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getError: ( - state: DeviceState, - action: PayloadAction - ) => { - state.errors = action.payload; - state = setErrors(state, action, "get"); - state.loading = false; - state.saving = false; - }, - getStart: (state: DeviceState) => { - state.loading = true; - }, - getSuccess: (state: DeviceState, action: PayloadAction) => { - const device = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: Device) => - draftItem[DeviceMeta.PK] === device[DeviceMeta.PK] - ); - if (i !== -1) { - state.items[i] = device; - } else { - state.items.push(device); - // Set up the statuses for this device. - state.statuses[device[DeviceMeta.PK]] = DEFAULT_STATUSES; - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: DeviceMeta.MODEL, + primaryKey: DeviceMeta.PK, + defaultStatuses: DEFAULT_STATUSES, + setErrors, + }), linkSubnet: { prepare: (params: LinkSubnetParams) => ({ meta: { diff --git a/src/app/store/domain/reducers.test.ts b/src/app/store/domain/reducers.test.ts index dac1826064..b7d9c3a59a 100644 --- a/src/app/store/domain/reducers.test.ts +++ b/src/app/store/domain/reducers.test.ts @@ -199,21 +199,6 @@ describe("domain reducer", () => { ); }); - // Related to: https://bugs.launchpad.net/maas/+bug/1931654. - it("reduces getError when the error when a domain can't be found", () => { - const domainState = factory.domainState({ - errors: null, - saving: true, - }); - - expect(reducers(domainState, actions.getError("9"))).toEqual( - factory.domainState({ - errors: "There was an error getting the domain.", - saving: false, - }) - ); - }); - it("reduces setDefaultError", () => { const domainState = factory.domainState({ errors: null, @@ -230,21 +215,6 @@ describe("domain reducer", () => { ); }); - // Related to: https://bugs.launchpad.net/maas/+bug/1931654. - it("reduces setDefaultError when the error when a domain can't be found", () => { - const domainState = factory.domainState({ - errors: null, - saving: true, - }); - - expect(reducers(domainState, actions.setDefaultError("9"))).toEqual( - factory.domainState({ - errors: "There was an error when setting default domain.", - saving: false, - }) - ); - }); - it("reduces setDefaultSuccess", () => { const domain1 = factory.domain({ id: 1, is_default: true }); const domain2 = factory.domain({ id: 2, is_default: false }); @@ -291,20 +261,6 @@ describe("domain reducer", () => { ); }); - // Related to: https://bugs.launchpad.net/maas/+bug/1931654. - it("reduces setActiveError when the error when a domain can't be found", () => { - const domainState = factory.domainState({ - errors: null, - }); - - expect(reducers(domainState, actions.setActiveError("9"))).toEqual( - factory.domainState({ - errors: "There was an error when setting active domain.", - saving: false, - }) - ); - }); - it("reduces setActiveSuccess", () => { const podState = factory.domainState({ active: null, diff --git a/src/app/store/domain/slice.ts b/src/app/store/domain/slice.ts index 8e8affc6a7..0021f9aaa7 100644 --- a/src/app/store/domain/slice.ts +++ b/src/app/store/domain/slice.ts @@ -23,6 +23,7 @@ import type { import type { APIError } from "@/app/base/types"; import { generateCommonReducers, + generateGetReducers, genericInitialState, } from "@/app/store/utils/slice"; @@ -39,55 +40,10 @@ const domainSlice = createSlice({ CreateParams, UpdateParams >(DomainMeta.MODEL, DomainMeta.PK), - get: { - prepare: (id: Domain[DomainMeta.PK]) => ({ - meta: { - model: DomainMeta.MODEL, - method: "get", - }, - payload: { - params: { id }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getStart: (state: DomainState) => { - state.loading = true; - }, - getError: ( - state: DomainState, - action: PayloadAction - ) => { - // API seems to return the domain id in payload.error not an error message - // when the domain can't be found. This override can be removed when the - // bug is fixed: https://bugs.launchpad.net/maas/+bug/1931654. - if (!isNaN(Number(action.payload))) { - // returned error string is a number (id of the domain) - state.errors = "There was an error getting the domain."; - } else { - // returned error string is an error message - state.errors = action.payload; - } - - state.loading = false; - state.saving = false; - }, - getSuccess: (state: DomainState, action: PayloadAction) => { - const domain = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: Domain) => draftItem.id === domain.id - ); - if (i !== -1) { - state.items[i] = domain; - } else { - state.items.push(domain); - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: DomainMeta.MODEL, + primaryKey: DomainMeta.PK, + }), setDefault: { prepare: (id: Domain[DomainMeta.PK]) => ({ meta: { @@ -111,16 +67,7 @@ const domainSlice = createSlice({ action: PayloadAction ) => { state.saving = false; - // API seems to return the domain id in payload.error not an error message - // when the domain can't be found. This override can be removed when the - // bug is fixed: https://bugs.launchpad.net/maas/+bug/1931654. - if (!isNaN(Number(action.payload))) { - // returned error string is a number (id of the domain) - state.errors = "There was an error when setting default domain."; - } else { - // returned error string is an error message - state.errors = action.payload; - } + state.errors = action.payload; }, setDefaultSuccess: (state: DomainState, action: PayloadAction) => { state.saving = false; @@ -156,16 +103,7 @@ const domainSlice = createSlice({ action: PayloadAction ) => { state.active = null; - // API seems to return the domain id in payload.error not an error message - // when the domain can't be found. This override can be removed when the - // bug is fixed: https://bugs.launchpad.net/maas/+bug/1931654. - if (!isNaN(Number(action.payload))) { - // returned error string is a number (id of the domain) - state.errors = "There was an error when setting active domain."; - } else { - // returned error string is an error message - state.errors = action.payload; - } + state.errors = action.payload; }, setActiveSuccess: ( state: DomainState, diff --git a/src/app/store/fabric/slice.ts b/src/app/store/fabric/slice.ts index dd4957a89e..db27439dae 100644 --- a/src/app/store/fabric/slice.ts +++ b/src/app/store/fabric/slice.ts @@ -6,6 +6,7 @@ import type { CreateParams, Fabric, FabricState, UpdateParams } from "./types"; import { generateCommonReducers, + generateGetReducers, genericInitialState, } from "@/app/store/utils/slice"; @@ -22,46 +23,10 @@ const fabricSlice = createSlice({ CreateParams, UpdateParams >(FabricMeta.MODEL, FabricMeta.PK), - get: { - prepare: (id: Fabric[FabricMeta.PK]) => ({ - meta: { - model: FabricMeta.MODEL, - method: "get", - }, - payload: { - params: { [FabricMeta.PK]: id }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getError: ( - state: FabricState, - action: PayloadAction - ) => { - state.errors = action.payload; - state.loading = false; - state.saving = false; - }, - getStart: (state: FabricState) => { - state.loading = true; - }, - getSuccess: (state: FabricState, action: PayloadAction) => { - const fabric = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: Fabric) => - draftItem[FabricMeta.PK] === fabric[FabricMeta.PK] - ); - if (i !== -1) { - state.items[i] = fabric; - } else { - state.items.push(fabric); - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: FabricMeta.MODEL, + primaryKey: FabricMeta.PK, + }), setActive: { prepare: (id: Fabric[FabricMeta.PK] | null) => ({ meta: { diff --git a/src/app/store/iprange/slice.ts b/src/app/store/iprange/slice.ts index 27f7419bc1..28eec96939 100644 --- a/src/app/store/iprange/slice.ts +++ b/src/app/store/iprange/slice.ts @@ -1,4 +1,3 @@ -import type { PayloadAction } from "@reduxjs/toolkit"; import { createSlice } from "@reduxjs/toolkit"; import { IPRangeMeta } from "./types"; @@ -11,6 +10,7 @@ import type { import { generateCommonReducers, + generateGetReducers, genericInitialState, } from "@/app/store/utils/slice"; @@ -24,46 +24,10 @@ const ipRangeSlice = createSlice({ CreateParams, UpdateParams >(IPRangeMeta.MODEL, IPRangeMeta.PK), - - get: { - prepare: (id: IPRange[IPRangeMeta.PK]) => ({ - meta: { - model: IPRangeMeta.MODEL, - method: "get", - }, - payload: { - params: { id }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getStart: (state: IPRangeState) => { - state.loading = true; - }, - getError: ( - state: IPRangeState, - action: PayloadAction - ) => { - state.errors = action.payload; - state.loading = false; - state.saving = false; - }, - getSuccess: (state: IPRangeState, action: PayloadAction) => { - const ipRange = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: IPRange) => draftItem.id === ipRange.id - ); - if (i !== -1) { - state.items[i] = ipRange; - } else { - state.items.push(ipRange); - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: IPRangeMeta.MODEL, + primaryKey: IPRangeMeta.PK, + }), }, }); diff --git a/src/app/store/pod/slice.ts b/src/app/store/pod/slice.ts index c3a3008145..f1261e2f8a 100644 --- a/src/app/store/pod/slice.ts +++ b/src/app/store/pod/slice.ts @@ -19,6 +19,7 @@ import { generateStatusHandlers } from "@/app/store/utils"; import type { GenericItemMeta } from "@/app/store/utils"; import { generateCommonReducers, + generateGetReducers, genericInitialState, } from "@/app/store/utils/slice"; @@ -151,44 +152,11 @@ const podSlice = createSlice({ } }); }, - get: { - prepare: (podID: Pod[PodMeta.PK]) => ({ - meta: { - model: PodMeta.MODEL, - method: "get", - }, - payload: { - params: { id: podID }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getStart: (state: PodState) => { - state.loading = true; - }, - getError: (state: PodState, action: PayloadAction) => { - state.errors = action.payload; - state.loading = false; - state.saving = false; - }, - getSuccess: (state: PodState, action: PayloadAction) => { - const pod = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: Pod) => draftItem.id === pod.id - ); - if (i !== -1) { - state.items[i] = pod; - } else { - state.items.push(pod); - // Set up the statuses for this pod. - state.statuses[pod.id] = DEFAULT_STATUSES; - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: PodMeta.MODEL, + primaryKey: PodMeta.PK, + defaultStatuses: DEFAULT_STATUSES, + }), getProjects: { prepare: (params: GetProjectsParams) => ({ meta: { diff --git a/src/app/store/space/slice.ts b/src/app/store/space/slice.ts index 79d2a8c196..bf5e6aa8c2 100644 --- a/src/app/store/space/slice.ts +++ b/src/app/store/space/slice.ts @@ -6,6 +6,7 @@ import type { CreateParams, Space, SpaceState, UpdateParams } from "./types"; import { generateCommonReducers, + generateGetReducers, genericInitialState, } from "@/app/store/utils/slice"; @@ -22,45 +23,10 @@ const spaceSlice = createSlice({ CreateParams, UpdateParams >(SpaceMeta.MODEL, SpaceMeta.PK), - get: { - prepare: (id: Space[SpaceMeta.PK]) => ({ - meta: { - model: SpaceMeta.MODEL, - method: "get", - }, - payload: { - params: { [SpaceMeta.PK]: id }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getError: ( - state: SpaceState, - action: PayloadAction - ) => { - state.errors = action.payload; - state.loading = false; - state.saving = false; - }, - getStart: (state: SpaceState) => { - state.loading = true; - }, - getSuccess: (state: SpaceState, action: PayloadAction) => { - const space = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: Space) => draftItem[SpaceMeta.PK] === space[SpaceMeta.PK] - ); - if (i !== -1) { - state.items[i] = space; - } else { - state.items.push(space); - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: SpaceMeta.MODEL, + primaryKey: SpaceMeta.PK, + }), setActive: { prepare: (id: Space[SpaceMeta.PK] | null) => ({ meta: { diff --git a/src/app/store/subnet/slice.ts b/src/app/store/subnet/slice.ts index 1d1f039427..46008ef562 100644 --- a/src/app/store/subnet/slice.ts +++ b/src/app/store/subnet/slice.ts @@ -15,6 +15,7 @@ import { genericInitialState, generateStatusHandlers, updateErrors, + generateGetReducers, } from "@/app/store/utils/slice"; import type { GenericItemMeta } from "@/app/store/utils/slice"; import { isId } from "@/app/utils"; @@ -92,47 +93,11 @@ const subnetSlice = createSlice({ state.loading = false; state.loaded = true; }, - get: { - prepare: (id: Subnet[SubnetMeta.PK]) => ({ - meta: { - model: SubnetMeta.MODEL, - method: "get", - }, - payload: { - params: { [SubnetMeta.PK]: id }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getError: ( - state: SubnetState, - action: PayloadAction - ) => { - state.errors = action.payload; - state.loading = false; - state.saving = false; - }, - getStart: (state: SubnetState) => { - state.loading = true; - }, - getSuccess: (state: SubnetState, action: PayloadAction) => { - const subnet = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: Subnet) => - draftItem[SubnetMeta.PK] === subnet[SubnetMeta.PK] - ); - if (i !== -1) { - state.items[i] = subnet; - } else { - state.items.push(subnet); - state.statuses[subnet[SubnetMeta.PK]] = DEFAULT_STATUSES; - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: SubnetMeta.MODEL, + primaryKey: SubnetMeta.PK, + defaultStatuses: DEFAULT_STATUSES, + }), scan: { prepare: (id: Subnet[SubnetMeta.PK]) => ({ meta: { diff --git a/src/app/store/utils/slice.ts b/src/app/store/utils/slice.ts index 1c851f7ae3..350f305fab 100644 --- a/src/app/store/utils/slice.ts +++ b/src/app/store/utils/slice.ts @@ -500,3 +500,73 @@ export const generateStatusHandlers = < }, {} ); + +export const generateGetReducers = < + S extends CommonStateTypes | StatusStateTypes | EventErrorStateTypes, + T extends S["items"][0], + K extends keyof T, +>({ + modelName, + primaryKey, + defaultStatuses, + setErrors, +}: { + modelName: keyof SliceState; + primaryKey: K; + defaultStatuses?: S extends StatusStateTypes + ? S["statuses"][T[K] & keyof S["statuses"]] + : never; + setErrors?: ( + state: S, + action: PayloadAction | null, + event: string | null + ) => S; +}) => { + return { + get: { + prepare: (id: T[K]) => ({ + meta: { + model: modelName, + method: "get", + }, + payload: { + params: { + [primaryKey]: id, + }, + }, + }), + reducer: () => { + // No state changes need to be handled for this action. + }, + }, + getStart: (state: S) => { + state.loading = true; + }, + getError: (state: S, action: PayloadAction) => { + state.errors = action.payload; + if (setErrors) { + state = setErrors(state, action, "get"); + } + state.loading = false; + state.saving = false; + }, + getSuccess: (state: S, action: PayloadAction) => { + const item = action.payload; + const index = (state.items as T[]).findIndex( + (draftItem: T) => draftItem[primaryKey] === item[primaryKey] + ); + if (index !== -1) { + state.items[index] = item; + } else { + (state.items as T[]).push(item); + if ("statuses" in state && defaultStatuses) { + (state.statuses as StatusStateTypes["statuses"])[ + item[primaryKey] as keyof StatusStateTypes["statuses"] + ] = defaultStatuses; + } + } + state.loading = false; + state.saving = false; + }, + }; +}; diff --git a/src/app/store/vlan/slice.ts b/src/app/store/vlan/slice.ts index 56c2833859..f3bdca7ecf 100644 --- a/src/app/store/vlan/slice.ts +++ b/src/app/store/vlan/slice.ts @@ -14,6 +14,7 @@ import type { import { generateCommonReducers, + generateGetReducers, generateStatusHandlers, genericInitialState, updateErrors, @@ -102,46 +103,11 @@ const vlanSlice = createSlice({ state.loading = false; state.loaded = true; }, - get: { - prepare: (id: VLAN[VLANMeta.PK]) => ({ - meta: { - model: VLANMeta.MODEL, - method: "get", - }, - payload: { - params: { [VLANMeta.PK]: id }, - }, - }), - reducer: () => { - // No state changes need to be handled for this action. - }, - }, - getError: ( - state: VLANState, - action: PayloadAction - ) => { - state.errors = action.payload; - state.loading = false; - state.saving = false; - }, - getStart: (state: VLANState) => { - state.loading = true; - }, - getSuccess: (state: VLANState, action: PayloadAction) => { - const vlan = action.payload; - // If the item already exists, update it, otherwise - // add it to the store. - const i = state.items.findIndex( - (draftItem: VLAN) => draftItem[VLANMeta.PK] === vlan[VLANMeta.PK] - ); - if (i !== -1) { - state.items[i] = vlan; - } else { - state.items.push(vlan); - state.statuses[vlan[VLANMeta.PK]] = DEFAULT_STATUSES; - } - state.loading = false; - }, + ...generateGetReducers({ + modelName: VLANMeta.MODEL, + primaryKey: VLANMeta.PK, + defaultStatuses: DEFAULT_STATUSES, + }), setActive: { prepare: (id: VLAN[VLANMeta.PK] | null) => ({ meta: {