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 && (
-
- {generateSelectedItems({ selectedTags, updateTags })}
-
- )}
-
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 && (
-
-
- {generateDropdownItems({
- allowNewTags,
- filter,
- selectedTags,
- tags,
- updateTags,
- })}
+ setDropdownOpen(true)}>{label}}
+ >
+ {selectedTags.length > 0 && (
+
+ {generateSelectedItems({ selectedTags, updateTags })}
-
- )}
+ )}
+
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 && (
+
+
+ {generateDropdownItems({
+ allowNewTags,
+ filter,
+ selectedTags,
+ tags,
+ updateTags,
+ })}
+
+
+ )}
+
);
};
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,
});
};