diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/ActionFormWrapper.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/ActionFormWrapper.js
index f9f7ad1e81..2913026ce5 100644
--- a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/ActionFormWrapper.js
+++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/ActionFormWrapper.js
@@ -8,6 +8,7 @@ import { machine as machineActions } from "app/base/actions";
import { machine as machineSelectors } from "app/base/selectors";
import ActionForm from "./ActionForm";
import DeployForm from "./DeployForm";
+import SetPoolForm from "./SetPoolForm";
import SetZoneForm from "./SetZoneForm";
const getErrorSentence = (action, count) => {
@@ -61,6 +62,8 @@ export const ActionFormWrapper = ({ selectedAction, setSelectedAction }) => {
switch (selectedAction.name) {
case "deploy":
return ;
+ case "set-pool":
+ return ;
case "set-zone":
return ;
default:
diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.js
new file mode 100644
index 0000000000..7b2d4b6d2f
--- /dev/null
+++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.js
@@ -0,0 +1,73 @@
+import { useDispatch, useSelector } from "react-redux";
+import * as Yup from "yup";
+import pluralize from "pluralize";
+import React from "react";
+
+import {
+ machine as machineActions,
+ resourcepool as resourcePoolActions,
+} from "app/base/actions";
+import {
+ machine as machineSelectors,
+ resourcepool as resourcePoolSelectors,
+} from "app/base/selectors";
+import FormikForm from "app/base/components/FormikForm";
+import FormCardButtons from "app/base/components/FormCardButtons";
+import SetPoolFormFields from "./SetPoolFormFields";
+
+const SetPoolSchema = Yup.object().shape({
+ description: Yup.string(),
+ name: Yup.string().required("Resource pool required"),
+ poolSelection: Yup.string().oneOf(["create", "select"]).required(),
+});
+
+export const SetPoolForm = ({ setSelectedAction }) => {
+ const dispatch = useDispatch();
+
+ const selectedMachines = useSelector(machineSelectors.selected);
+ const saved = useSelector(machineSelectors.saved);
+ const saving = useSelector(machineSelectors.saving);
+ const errors = useSelector(machineSelectors.errors);
+ const resourcePools = useSelector(resourcePoolSelectors.all);
+
+ return (
+ setSelectedAction(null)}
+ onSaveAnalytics={{
+ action: "Set resource pool",
+ category: "Take action menu",
+ label: "Set resource pool of selected machines",
+ }}
+ onSubmit={(values) => {
+ if (values.poolSelection === "create") {
+ // TODO: Add method for creating a pool then setting selected machines to it
+ // https://github.com/canonical-web-and-design/maas-ui/issues/928
+ dispatch(resourcePoolActions.create(values));
+ }
+ const pool = resourcePools.find((pool) => pool.name === values.name);
+ if (pool) {
+ selectedMachines.forEach((machine) => {
+ dispatch(machineActions.setPool(machine.system_id, pool.id));
+ });
+ }
+ setSelectedAction(null);
+ }}
+ saving={saving}
+ saved={saved}
+ validationSchema={SetPoolSchema}
+ >
+
+
+ );
+};
+
+export default SetPoolForm;
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
new file mode 100644
index 0000000000..636f6bf830
--- /dev/null
+++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolForm.test.js
@@ -0,0 +1,94 @@
+import { act } from "react-dom/test-utils";
+import { MemoryRouter } from "react-router-dom";
+import { mount } from "enzyme";
+import { Provider } from "react-redux";
+import configureStore from "redux-mock-store";
+import React from "react";
+
+import SetPoolForm from "./SetPoolForm";
+
+const mockStore = configureStore();
+
+describe("SetPoolForm", () => {
+ let state;
+ beforeEach(() => {
+ state = {
+ machine: {
+ errors: {},
+ loading: false,
+ loaded: true,
+ items: [
+ {
+ system_id: "abc123",
+ },
+ {
+ system_id: "def456",
+ },
+ ],
+ selected: [],
+ },
+ resourcepool: {
+ items: [
+ { id: 0, name: "default" },
+ { id: 1, name: "pool-1" },
+ ],
+ },
+ };
+ });
+
+ it("correctly dispatches actions to set pools of selected machines", () => {
+ const store = mockStore(state);
+ state.machine.selected = ["abc123", "def456"];
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ act(() =>
+ wrapper.find("Formik").props().onSubmit({
+ poolSelection: "select",
+ name: "pool-1",
+ description: "",
+ })
+ );
+ expect(store.getActions()).toStrictEqual([
+ {
+ type: "SET_MACHINE_POOL",
+ meta: {
+ model: "machine",
+ method: "action",
+ },
+ payload: {
+ params: {
+ action: "set-pool",
+ extra: {
+ pool_id: 1,
+ },
+ system_id: "abc123",
+ },
+ },
+ },
+ {
+ type: "SET_MACHINE_POOL",
+ meta: {
+ model: "machine",
+ method: "action",
+ },
+ payload: {
+ params: {
+ action: "set-pool",
+ extra: {
+ pool_id: 1,
+ },
+ system_id: "def456",
+ },
+ },
+ },
+ ]);
+ });
+});
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
new file mode 100644
index 0000000000..b1bbc19792
--- /dev/null
+++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/SetPoolFormFields.js
@@ -0,0 +1,68 @@
+import { Col, Row, Select } from "@canonical/react-components";
+import { useFormikContext } from "formik";
+import { useSelector } from "react-redux";
+import React from "react";
+
+import { resourcepool as resourcePoolSelectors } from "app/base/selectors";
+
+import FormikField from "app/base/components/FormikField";
+
+export const SetPoolFormFields = () => {
+ const resourcePools = useSelector(resourcePoolSelectors.all);
+ const { values } = useFormikContext();
+
+ const resourcePoolOptions = [
+ { label: "Select resource pool", value: "", disabled: true },
+ ...resourcePools.map((pool) => ({
+ key: `pool-${pool.id}`,
+ label: pool.name,
+ value: pool.name,
+ })),
+ ];
+
+ return (
+
+
+
+ -
+
+
+ -
+ {/* Disabled until we have a method for handling sequenced websocket requests.
+ https://github.com/canonical-web-and-design/maas-ui/issues/928 */}
+
+
+
+ {values.poolSelection === "select" ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
+
+
+ );
+};
+
+export default SetPoolFormFields;
diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/SetPoolFormFields.test.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/SetPoolFormFields.test.js
new file mode 100644
index 0000000000..e5137a59d4
--- /dev/null
+++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/SetPoolFormFields.test.js
@@ -0,0 +1,79 @@
+import { act } from "react-dom/test-utils";
+import { MemoryRouter } from "react-router-dom";
+import { mount } from "enzyme";
+import { Provider } from "react-redux";
+import configureStore from "redux-mock-store";
+import React from "react";
+
+import SetPoolForm from "../SetPoolForm";
+
+const mockStore = configureStore();
+
+describe("SetPoolFormFields", () => {
+ let state;
+ beforeEach(() => {
+ state = {
+ machine: {
+ errors: {},
+ loading: false,
+ loaded: true,
+ items: [
+ {
+ system_id: "abc123",
+ },
+ {
+ system_id: "def456",
+ },
+ ],
+ selected: ["abc123", "def456"],
+ },
+ resourcepool: {
+ items: [
+ { id: 0, name: "default" },
+ { id: 1, name: "pool-1" },
+ ],
+ },
+ };
+ });
+
+ it("shows a select if select pool radio chosen", async () => {
+ const store = mockStore(state);
+ const wrapper = mount(
+
+
+
+
+
+ );
+ await act(async () => {
+ wrapper.find("[data-test='select-pool'] input").simulate("change", {
+ target: { name: "poolSelection", value: "select" },
+ });
+ });
+ wrapper.update();
+ expect(wrapper.find("Select").exists()).toBe(true);
+ });
+
+ it.skip("shows inputs for creating a pool if create pool radio chosen", async () => {
+ const store = mockStore(state);
+ const wrapper = mount(
+
+
+
+
+
+ );
+ await act(async () => {
+ wrapper.find("[data-test='create-pool'] input").simulate("change", {
+ target: { name: "poolSelection", value: "create" },
+ });
+ });
+ wrapper.update();
+ expect(wrapper.find("Input[name='name']").exists()).toBe(true);
+ expect(wrapper.find("Input[name='description']").exists()).toBe(true);
+ });
+});
diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/index.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/index.js
new file mode 100644
index 0000000000..f5eaa3f47b
--- /dev/null
+++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/SetPoolFormFields/index.js
@@ -0,0 +1 @@
+export { default } from "./SetPoolFormFields";
diff --git a/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/index.js b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/index.js
new file mode 100644
index 0000000000..dd63228c16
--- /dev/null
+++ b/ui/src/app/machines/components/HeaderStrip/ActionFormWrapper/SetPoolForm/index.js
@@ -0,0 +1 @@
+export { default } from "./SetPoolForm";