Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build set pool form in action menu. #891

Merged
merged 2 commits into from
Apr 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -61,6 +62,8 @@ export const ActionFormWrapper = ({ selectedAction, setSelectedAction }) => {
switch (selectedAction.name) {
case "deploy":
return <DeployForm setSelectedAction={setSelectedAction} />;
case "set-pool":
return <SetPoolForm setSelectedAction={setSelectedAction} />;
case "set-zone":
return <SetZoneForm setSelectedAction={setSelectedAction} />;
default:
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<FormikForm
buttons={FormCardButtons}
buttonsBordered={false}
errors={errors}
cleanup={machineActions.cleanup}
initialValues={{ poolSelection: "select", description: "", name: "" }}
submitLabel={`Set resource pool of ${selectedMachines.length} ${pluralize(
"machine",
selectedMachines.length
)}`}
onCancel={() => 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}
>
<SetPoolFormFields />
</FormikForm>
);
};

export default SetPoolForm;
Original file line number Diff line number Diff line change
@@ -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(
<Provider store={store}>
<MemoryRouter
initialEntries={[{ pathname: "/machines", key: "testKey" }]}
>
<SetPoolForm setSelectedAction={jest.fn()} />
</MemoryRouter>
</Provider>
);

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",
},
},
},
]);
});
});
Original file line number Diff line number Diff line change
@@ -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 (
<Row>
<Col size="6">
<ul className="p-inline-list u-equal-height u-no-margin--bottom">
<li className="p-inline-list__item">
<FormikField
data-test="select-pool"
label="Select pool"
name="poolSelection"
type="radio"
value="select"
/>
</li>
<li className="p-inline-list__item">
{/* Disabled until we have a method for handling sequenced websocket requests.
https://github.com/canonical-web-and-design/maas-ui/issues/928 */}
<FormikField
data-test="create-pool"
disabled
label="Create pool"
name="poolSelection"
type="radio"
value="create"
/>
</li>
</ul>
{values.poolSelection === "select" ? (
<FormikField
component={Select}
label="Pool"
name="name"
options={resourcePoolOptions}
required
/>
) : (
<>
<FormikField label="Name" name="name" required type="text" />
<FormikField label="Description" name="description" type="text" />
</>
)}
</Col>
</Row>
);
};

export default SetPoolFormFields;
Original file line number Diff line number Diff line change
@@ -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(
<Provider store={store}>
<MemoryRouter
initialEntries={[{ pathname: "/machines", key: "testKey" }]}
>
<SetPoolForm setSelectedAction={jest.fn()} />
</MemoryRouter>
</Provider>
);
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(
<Provider store={store}>
<MemoryRouter
initialEntries={[{ pathname: "/machines", key: "testKey" }]}
>
<SetPoolForm setSelectedAction={jest.fn()} />
</MemoryRouter>
</Provider>
);
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);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./SetPoolFormFields";
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./SetPoolForm";