diff --git a/ui/src/app/base/components/TagSelector/TagSelector.js b/ui/src/app/base/components/TagSelector/TagSelector.js index c3917c1b77..da98b2217f 100644 --- a/ui/src/app/base/components/TagSelector/TagSelector.js +++ b/ui/src/app/base/components/TagSelector/TagSelector.js @@ -1,4 +1,5 @@ import { Button, Input, Label } from "@canonical/react-components"; +import Field from "@canonical/react-components/dist/components/Field"; import classNames from "classnames"; import React, { useEffect, useRef, useState } from "react"; @@ -116,6 +117,8 @@ const generateSelectedItems = ({ selectedTags, updateTags }) => export const TagSelector = ({ allowNewTags = false, disabled, + error, + help, initialSelected = [], label, onTagsUpdate, @@ -157,45 +160,50 @@ export const TagSelector = ({ return (
- - {selectedTags.length > 0 && ( - - )} - 0, - })} - disabled={disabled} - onChange={(e) => setFilter(e.target.value)} - onFocus={() => setDropdownOpen(true)} - onKeyPress={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - if (allowNewTags) { - updateTags([...selectedTags, sanitiseFilter(filter)]); - } - } - }} - placeholder={placeholder} - required={required} - type="text" - value={filter} - /> - {dropdownOpen && ( -
-
- )} + )} + 0, + })} + disabled={disabled} + onChange={(e) => setFilter(e.target.value)} + onFocus={() => setDropdownOpen(true)} + onKeyPress={(e) => { + if (e.key === "Enter") { + e.preventDefault(); + if (allowNewTags) { + updateTags([...selectedTags, sanitiseFilter(filter)]); + } + } + }} + placeholder={placeholder} + required={required} + type="text" + value={filter} + /> + {dropdownOpen && ( +
+ +
+ )} +
); }; diff --git a/ui/src/app/base/components/TagSelector/TagSelector.test.js b/ui/src/app/base/components/TagSelector/TagSelector.test.js index ccde08ff84..451de96895 100644 --- a/ui/src/app/base/components/TagSelector/TagSelector.test.js +++ b/ui/src/app/base/components/TagSelector/TagSelector.test.js @@ -1,4 +1,4 @@ -import { shallow } from "enzyme"; +import { mount, shallow } from "enzyme"; import React from "react"; import TagSelector from "./TagSelector"; @@ -26,7 +26,7 @@ describe("TagSelector", () => { }); it("renders and matches the snapshot when opened", () => { - const component = shallow( + const component = mount( { tags={tags} /> ); - component.find("Label").simulate("click"); + component.find("Label").first().simulate("click"); expect(component).toMatchSnapshot(); }); it("renders and matches the snapshot with tag descriptions", () => { - const component = shallow( + const component = mount( { ]} /> ); - component.find("Label").simulate("click"); + component.find("Label").first().simulate("click"); expect(component).toMatchSnapshot(); }); diff --git a/ui/src/app/base/components/TagSelector/__snapshots__/TagSelector.test.js.snap b/ui/src/app/base/components/TagSelector/__snapshots__/TagSelector.test.js.snap index b2634aa418..1b8b8e0771 100644 --- a/ui/src/app/base/components/TagSelector/__snapshots__/TagSelector.test.js.snap +++ b/ui/src/app/base/components/TagSelector/__snapshots__/TagSelector.test.js.snap @@ -4,143 +4,206 @@ exports[`TagSelector renders and matches the snapshot when closed 1`] = `
- + } > - Tags - - + +
`; exports[`TagSelector renders and matches the snapshot when opened 1`] = ` -
- -
-
    -
  • - -
  • -
  • + } + > +
    - -
  • -
+ + +
+
+ +
+
+
+ +
+
+ - +
`; exports[`TagSelector renders and matches the snapshot with tag descriptions 1`] = ` -
- -
-
    -
  • - -
  • -
  • + } + > +
    - -
  • -
+ +
+
+ +
+
+
+ +
+
+ - +
`; diff --git a/ui/src/app/base/components/TagSelector/_index.scss b/ui/src/app/base/components/TagSelector/_index.scss index 7d9e7132ce..890a382ac7 100644 --- a/ui/src/app/base/components/TagSelector/_index.scss +++ b/ui/src/app/base/components/TagSelector/_index.scss @@ -5,6 +5,10 @@ position: relative; } + .tag-selector .p-form-validation__message { + margin-top: 0; + } + .tag-selector__input { margin-bottom: 0; position: relative; @@ -65,6 +69,10 @@ padding: $spv-inner--x-small $sph-inner--small; } + .is-error .tag-selector__selected-list { + border-color: $color-negative; + } + .tag-selector__selected-item { @include vf-inline-list-item; } diff --git a/ui/src/app/base/reducers/machine/machine.js b/ui/src/app/base/reducers/machine/machine.js index 130bf83563..755f567b17 100644 --- a/ui/src/app/base/reducers/machine/machine.js +++ b/ui/src/app/base/reducers/machine/machine.js @@ -94,6 +94,7 @@ const machine = createNextState( ACTIONS.forEach(({ status, type }) => { switch (action.type) { case `${type}_START`: + draft.errors = {}; draft.statuses[action.meta.item.system_id][status] = true; break; case `${type}_SUCCESS`: @@ -150,6 +151,7 @@ const machine = createNextState( break; case "CREATE_MACHINE_NOTIFY": draft.items.push(action.payload); + draft.statuses[action.payload.system_id] = DEFAULT_STATUSES; break; case "DELETE_MACHINE_NOTIFY": draft.items = draft.items.filter( diff --git a/ui/src/app/base/reducers/machine/machine.test.js b/ui/src/app/base/reducers/machine/machine.test.js index e18839ac8d..859aac8409 100644 --- a/ui/src/app/base/reducers/machine/machine.test.js +++ b/ui/src/app/base/reducers/machine/machine.test.js @@ -1,5 +1,28 @@ import machine from "./machine"; +const STATUSES = { + aborting: false, + acquiring: false, + checkingPower: false, + commissioning: false, + deleting: false, + deploying: false, + enteringRescueMode: false, + exitingRescueMode: false, + locking: false, + markingBroken: false, + markingFixed: false, + overridingFailedTesting: false, + releasing: false, + settingPool: false, + settingZone: false, + tagging: false, + testing: false, + turningOff: false, + turningOn: false, + unlocking: false, +}; + describe("machine reducer", () => { it("should return the initial state", () => { expect(machine(undefined, {})).toEqual({ @@ -32,28 +55,6 @@ describe("machine reducer", () => { }); it("should correctly reduce FETCH_MACHINE_SUCCESS", () => { - const statuses = { - aborting: false, - acquiring: false, - checkingPower: false, - commissioning: false, - deleting: false, - deploying: false, - enteringRescueMode: false, - exitingRescueMode: false, - locking: false, - markingBroken: false, - markingFixed: false, - overridingFailedTesting: false, - releasing: false, - settingPool: false, - settingZone: false, - tagging: false, - testing: false, - turningOff: false, - turningOn: false, - unlocking: false, - }; expect( machine( { @@ -65,7 +66,7 @@ describe("machine reducer", () => { saving: false, selected: [], statuses: { - abc: statuses, + abc: STATUSES, }, }, { @@ -88,8 +89,8 @@ describe("machine reducer", () => { saving: false, selected: [], statuses: { - abc: statuses, - def: statuses, + abc: STATUSES, + def: STATUSES, }, }); }); @@ -215,9 +216,10 @@ describe("machine reducer", () => { saved: false, saving: false, selected: [], + statuses: {}, }, { - payload: { id: 2, name: "machine2" }, + payload: { id: 2, name: "machine2", system_id: "abc" }, type: "CREATE_MACHINE_NOTIFY", } ) @@ -225,13 +227,16 @@ describe("machine reducer", () => { errors: {}, items: [ { id: 1, name: "machine1" }, - { id: 2, name: "machine2" }, + { id: 2, name: "machine2", system_id: "abc" }, ], loaded: false, loading: false, saved: false, saving: false, selected: [], + statuses: { + abc: STATUSES, + }, }); }); @@ -375,6 +380,7 @@ describe("machine reducer", () => { expect( machine( { + errors: "Uh oh", statuses: { abc: { settingPool: false, @@ -391,6 +397,7 @@ describe("machine reducer", () => { } ) ).toEqual({ + errors: {}, statuses: { abc: { settingPool: true, diff --git a/ui/src/app/base/reducers/resourcepool/resourcepool.js b/ui/src/app/base/reducers/resourcepool/resourcepool.js index d649b8d58d..35979c738e 100644 --- a/ui/src/app/base/reducers/resourcepool/resourcepool.js +++ b/ui/src/app/base/reducers/resourcepool/resourcepool.js @@ -2,6 +2,10 @@ import { createStandardReducer } from "app/utils/redux"; import { resourcepool as poolActions } from "app/base/actions"; -const resourcepool = createStandardReducer(poolActions); +const resourcepool = createStandardReducer(poolActions, undefined, { + CREATE_RESOURCEPOOL_WITH_MACHINES: (state) => { + state.errors = {}; + }, +}); export default resourcepool; diff --git a/ui/src/app/base/reducers/resourcepool/resourcepool.test.js b/ui/src/app/base/reducers/resourcepool/resourcepool.test.js index 926e8bd63e..46468e9352 100644 --- a/ui/src/app/base/reducers/resourcepool/resourcepool.test.js +++ b/ui/src/app/base/reducers/resourcepool/resourcepool.test.js @@ -358,4 +358,19 @@ describe("resourcepool reducer", () => { saving: false, }); }); + + it("should correctly reduce CREATE_RESOURCEPOOL_WITH_MACHINES", () => { + expect( + resourcepool( + { + errors: { name: "Name already exists" }, + }, + { + type: "CREATE_RESOURCEPOOL_WITH_MACHINES", + } + ) + ).toEqual({ + errors: {}, + }); + }); }); diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/MachinesProcessing/MachinesProcessing.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/MachinesProcessing/MachinesProcessing.js index 741327130c..7edb6703ca 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/MachinesProcessing/MachinesProcessing.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/MachinesProcessing/MachinesProcessing.js @@ -8,21 +8,22 @@ import { machine as machineSelectors, } from "app/base/selectors"; -const useProcessingState = (savedState, setProcessing, setSelectedAction) => { - const previousSavedState = useRef(savedState); +// Handle checking when a value has cycled from false to true. +const useCycled = (newState, onCycled) => { + const previousState = useRef(newState); useEffect(() => { - if (savedState && !previousSavedState.current) { - setProcessing(false); - setSelectedAction(null); + if (newState && !previousState.current) { + onCycled(); } - if (previousSavedState.current !== savedState) { - previousSavedState.current = savedState; + if (previousState.current !== newState) { + previousState.current = newState; } - }, [savedState, setProcessing, setSelectedAction]); + }, [newState, onCycled]); }; const MachinesProcessing = ({ action, + hasErrors = false, machinesProcessing, setProcessing, setSelectedAction, @@ -41,11 +42,21 @@ const MachinesProcessing = ({ ? selectedMachinesCount - selectedSavingCount : 0; - useProcessingState( - selectedSavingCount === 0, - setProcessing, - setSelectedAction - ); + // If all the machines have finished processing then update the state. + useCycled(selectedSavingCount === 0, () => { + if (!hasErrors) { + setProcessing(false); + setSelectedAction(null); + } + }); + + // If the machines are processing and there are errors then exit the + // processing state. + useCycled(hasErrors, () => { + if (processingStarted) { + setProcessing(false); + } + }); return (

@@ -58,6 +69,7 @@ const MachinesProcessing = ({ MachinesProcessing.propTypes = { action: PropTypes.string.isRequired, + hasErrors: PropTypes.bool, machinesProcessing: PropTypes.array.isRequired, setProcessing: PropTypes.func.isRequired, setSelectedAction: PropTypes.func.isRequired, diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/MachinesProcessing/MachinesProcessing.test.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/MachinesProcessing/MachinesProcessing.test.js index 5df2135251..979b3dfcb0 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/MachinesProcessing/MachinesProcessing.test.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/MachinesProcessing/MachinesProcessing.test.js @@ -85,7 +85,7 @@ describe("MachinesProcessing", () => { ); }); - it("calls functions when processing is complete", () => { + it("updates the processing state when processing is complete", () => { const setProcessing = jest.fn(); const setSelectedAction = jest.fn(); const store = mockStore(state); @@ -110,4 +110,31 @@ describe("MachinesProcessing", () => { expect(setProcessing).toHaveBeenCalled(); expect(setSelectedAction).toHaveBeenCalled(); }); + + it("updates the processing state when there processing errors", () => { + const setProcessing = jest.fn(); + const setSelectedAction = jest.fn(); + const store = mockStore(state); + const Proxy = ({ machinesProcessing }) => ( + + + + + + ); + const wrapper = mount(); + expect(setProcessing).not.toHaveBeenCalled(); + expect(setSelectedAction).not.toHaveBeenCalled(); + wrapper.setProps({ hasErrors: true }); + expect(setSelectedAction).not.toHaveBeenCalled(); + wrapper.setProps({ machinesProcessing: [] }); + expect(setProcessing).toHaveBeenCalled(); + }); }); diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/OverrideTestForm/OverrideTestForm.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/OverrideTestForm/OverrideTestForm.js index 5119ec1063..4f17d44501 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/OverrideTestForm/OverrideTestForm.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/OverrideTestForm/OverrideTestForm.js @@ -1,6 +1,6 @@ import pluralize from "pluralize"; import { Col, Row, Loader } from "@canonical/react-components"; -import React, { useEffect } from "react"; +import React, { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import * as Yup from "yup"; @@ -12,6 +12,7 @@ import { import FormCardButtons from "app/base/components/FormCardButtons"; import FormikField from "app/base/components/FormikField"; import FormikForm from "app/base/components/FormikForm"; +import MachinesProcessing from "../MachinesProcessing"; const OverrideTestFormSchema = Yup.object().shape({ suppressResults: Yup.boolean(), @@ -19,18 +20,33 @@ const OverrideTestFormSchema = Yup.object().shape({ export const OverrideTestForm = ({ setSelectedAction }) => { const dispatch = useDispatch(); - + const [processing, setProcessing] = useState(false); const selectedMachines = useSelector(machineSelectors.selected); const saved = useSelector(machineSelectors.saved); const saving = useSelector(machineSelectors.saving); const errors = useSelector(machineSelectors.errors); const scriptResultsLoaded = useSelector(scriptresultsSelectors.loaded); const failedScriptResults = useSelector(machineSelectors.failedScriptResults); + const overridingFailedTestingSelected = useSelector( + machineSelectors.overridingFailedTestingSelected + ); useEffect(() => { dispatch(machineActions.fetchFailedScriptResults(selectedMachines)); }, [dispatch, selectedMachines]); + if (processing) { + return ( + 0} + machinesProcessing={overridingFailedTestingSelected} + setProcessing={setProcessing} + setSelectedAction={setSelectedAction} + action="override-failed-testing" + /> + ); + } + const generateFailedTestsMessage = ( selectedMachines, failedScriptResults @@ -114,7 +130,7 @@ export const OverrideTestForm = ({ setSelectedAction }) => { } }); } - setSelectedAction(null); + setProcessing(true); }} loading={!scriptResultsLoaded} saving={saving} diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/OverrideTestForm/OverrideTestForm.test.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/OverrideTestForm/OverrideTestForm.test.js index bfb279fc47..d6b1fee410 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/OverrideTestForm/OverrideTestForm.test.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/OverrideTestForm/OverrideTestForm.test.js @@ -13,6 +13,13 @@ describe("OverrideTestForm", () => { let initialState; beforeEach(() => { initialState = { + general: { + machineActions: { + data: [ + { name: "override-failed-testing", sentence: "change those pools" }, + ], + }, + }, machine: { errors: {}, loading: false, @@ -22,6 +29,10 @@ describe("OverrideTestForm", () => { { hostname: "host2", system_id: "def456" }, ], selected: [], + statuses: { + abc123: { settingPool: false }, + def456: { settingPool: false }, + }, }, scriptresults: { errors: {}, @@ -215,4 +226,26 @@ describe("OverrideTestForm", () => { }, ]); }); + + it("can render when processing machines", () => { + const state = { ...initialState }; + state.machine.selected = ["abc123", "def456"]; + const store = mockStore(state); + const wrapper = mount( + + + + + + ); + act(() => + wrapper.find("Formik").props().onSubmit({ + suppressResults: true, + }) + ); + wrapper.update(); + expect(wrapper.find("MachinesProcessing").exists()).toBe(true); + }); }); diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.js index 24781dca92..8bad3190b6 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.js @@ -1,7 +1,7 @@ import { useDispatch, useSelector } from "react-redux"; import * as Yup from "yup"; import pluralize from "pluralize"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { machine as machineActions, @@ -25,16 +25,33 @@ const SetPoolSchema = Yup.object().shape({ export const SetPoolForm = ({ setSelectedAction }) => { const dispatch = useDispatch(); const [processing, setProcessing] = useState(false); + const [initialValues, setInitialValues] = useState({ + poolSelection: "select", + description: "", + name: "", + }); const selectedMachines = useSelector(machineSelectors.selected); const saved = useSelector(machineSelectors.saved); const saving = useSelector(machineSelectors.saving); - const errors = useSelector(machineSelectors.errors); + const machineErrors = useSelector(machineSelectors.errors); + const poolErrors = useSelector(resourcePoolSelectors.errors); const resourcePools = useSelector(resourcePoolSelectors.all); const settingPoolSelected = useSelector(machineSelectors.settingPoolSelected); + const errors = + Object.keys(machineErrors).length > 0 ? machineErrors : poolErrors; + + useEffect( + () => () => { + dispatch(machineActions.cleanup()); + dispatch(resourcePoolActions.cleanup()); + }, + [dispatch] + ); if (processing) { return ( 0} machinesProcessing={settingPoolSelected} setProcessing={setProcessing} setSelectedAction={setSelectedAction} @@ -48,8 +65,7 @@ export const SetPoolForm = ({ setSelectedAction }) => { buttons={FormCardButtons} buttonsBordered={false} errors={errors} - cleanup={machineActions.cleanup} - initialValues={{ poolSelection: "select", description: "", name: "" }} + initialValues={initialValues} submitLabel={`Set resource pool of ${selectedMachines.length} ${pluralize( "machine", selectedMachines.length @@ -72,6 +88,9 @@ export const SetPoolForm = ({ setSelectedAction }) => { }); } } + // Store the values in case there are errors and the form needs to be + // displayed again. + setInitialValues(values); setProcessing(true); }} saving={saving} diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js index a506d2d90f..ba5e86039e 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js @@ -37,6 +37,7 @@ describe("SetPoolForm", () => { }, }, resourcepool: { + errors: {}, items: [ { id: 0, name: "default" }, { id: 1, name: "pool-1" }, @@ -98,7 +99,6 @@ describe("SetPoolForm", () => { }, }, }, - { type: "CLEANUP_MACHINE" }, ]); }); diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/SetPoolFormFields.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/SetPoolFormFields.js index f3a11ee28b..2eba329cdd 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/SetPoolFormFields.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/SetPoolFormFields.js @@ -9,7 +9,12 @@ import FormikField from "app/base/components/FormikField"; export const SetPoolFormFields = () => { const resourcePools = useSelector(resourcePoolSelectors.all); - const { values } = useFormikContext(); + const { + handleChange, + values, + setFieldValue, + setFieldTouched, + } = useFormikContext(); const resourcePoolOptions = [ { label: "Select resource pool", value: "", disabled: true }, @@ -20,6 +25,14 @@ export const SetPoolFormFields = () => { })), ]; + const handleRadioChange = (evt) => { + handleChange(evt); + // Reset the name field when changing the radio options otherwise the + // selected/provided name will appear in the different name inputs. + setFieldValue("name", ""); + setFieldTouched("name", false, false); + }; + return ( @@ -29,6 +42,7 @@ export const SetPoolFormFields = () => { data-test="select-pool" label="Select pool" name="poolSelection" + onChange={handleRadioChange} type="radio" value="select" /> @@ -38,6 +52,7 @@ export const SetPoolFormFields = () => { data-test="create-pool" label="Create pool" name="poolSelection" + onChange={handleRadioChange} type="radio" value="create" /> diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TagForm/TagForm.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TagForm/TagForm.js index d79086d12c..2a37a41f37 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TagForm/TagForm.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TagForm/TagForm.js @@ -25,15 +25,23 @@ const TagFormSchema = Yup.object().shape({ export const TagForm = ({ setSelectedAction }) => { const dispatch = useDispatch(); const [processing, setProcessing] = useState(false); + const [initialValues, setInitialValues] = useState({ tags: [] }); const selectedMachines = useSelector(machineSelectors.selected); const saved = useSelector(machineSelectors.saved); const saving = useSelector(machineSelectors.saving); const errors = useSelector(machineSelectors.errors); const taggingSelected = useSelector(machineSelectors.taggingSelected); + let formErrors = { ...errors }; + if (formErrors && formErrors.name) { + formErrors.tags = formErrors.name; + delete formErrors.name; + } + if (processing) { return ( 0} machinesProcessing={taggingSelected} setProcessing={setProcessing} setSelectedAction={setSelectedAction} @@ -46,9 +54,9 @@ export const TagForm = ({ setSelectedAction }) => { { dispatch(machineActions.tag(machine.system_id, values.tags)); }); } + setInitialValues(values); setProcessing(true); }} saving={saving} diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TagForm/TagFormFields/TagFormFields.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TagForm/TagFormFields/TagFormFields.js index d0d0fd28aa..df8c4e290d 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TagForm/TagFormFields/TagFormFields.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TagForm/TagFormFields/TagFormFields.js @@ -10,7 +10,7 @@ import TagSelector from "app/base/components/TagSelector"; export const TagFormFields = () => { const dispatch = useDispatch(); - const { setFieldValue } = useFormikContext(); + const { initialValues, setFieldValue } = useFormikContext(); const tags = useSelector(tagSelectors.all); const sortedTags = tags .map((tag) => ({ displayName: tag.name, name: tag.name })) @@ -26,6 +26,7 @@ export const TagFormFields = () => { setFieldValue("tags", selectedTags)} diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TestForm/TestForm.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TestForm/TestForm.js index 82abf7fa65..f46a88a96c 100644 --- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TestForm/TestForm.js +++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/TestForm/TestForm.js @@ -66,6 +66,7 @@ export const TestForm = ({ setSelectedAction }) => { if (processing) { return ( 0} machinesProcessing={testingSelected} setProcessing={setProcessing} setSelectedAction={setSelectedAction} diff --git a/ui/src/app/utils/redux.js b/ui/src/app/utils/redux.js index 708f85f70e..a91ab15868 100644 --- a/ui/src/app/utils/redux.js +++ b/ui/src/app/utils/redux.js @@ -84,7 +84,8 @@ export const createStandardReducer = ( loading: false, saved: false, saving: false, - } + }, + additionalReducers = {} ) => { return createReducer(initialState, { [actions.fetch.start]: (state) => { @@ -157,5 +158,6 @@ export const createStandardReducer = ( state.saved = false; state.saving = false; }, + ...additionalReducers, }); };