Skip to content

Commit

Permalink
feat: add menu option to submit playbook run for component
Browse files Browse the repository at this point in the history
Build on top of #1352  PR

Closes #1353

fix: close modal on save

fix: fix submit relaoding full page and wrong endpoint

fix: fix playbook submission params
  • Loading branch information
mainawycliffe committed Sep 20, 2023
1 parent a6fe0da commit 6f16c64
Show file tree
Hide file tree
Showing 8 changed files with 311 additions and 5 deletions.
9 changes: 9 additions & 0 deletions src/api/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ export const Auth = axios.create({
}
});

export const PlaybookAPI = axios.create({
baseURL: `${API_BASE}/playbook`,
headers: {
Accept: "application/json",
Prefer: "return=representation",
"Content-Type": "application/json"
}
});

export const Rback = axios.create({
baseURL: `${API_BASE}/rbac`,
headers: {
Expand Down
52 changes: 50 additions & 2 deletions src/api/query-hooks/playbooks.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { UseQueryOptions, useQuery } from "@tanstack/react-query";
import {
UseMutationOptions,
UseQueryOptions,
useMutation,
useQuery
} from "@tanstack/react-query";
import { PlaybookSpec } from "../../components/Playbooks/Settings/PlaybookSpecsTable";
import { getAllPlaybooksSpecs, getPlaybookSpec } from "../services/playbooks";
import {
getAllPlaybooksSpecs,
getPlaybookRun,
getPlaybookSpec,
submitPlaybookRun
} from "../services/playbooks";
import { SubmitPlaybookRunFormValues } from "../../components/Playbooks/Runs/SubmitPlaybookRunForm";

export function useGetAllPlaybookSpecs(
options: UseQueryOptions<PlaybookSpec[], Error> = {}
Expand All @@ -16,6 +27,27 @@ export function useGetAllPlaybookSpecs(
);
}

export type GetPlaybooksToRunParams = {
component_id?: string;
config_id?: string;
check_id?: string;
};

export function useGetPlaybooksToRun(
params: GetPlaybooksToRunParams,
options: UseQueryOptions<PlaybookSpec[], Error> = {}
) {
return useQuery<PlaybookSpec[], Error>(
["playbooks", "run", params],
() => getPlaybookRun(params),
{
cacheTime: 0,
staleTime: 0,
...options
}
);
}

export function useGetPlaybookSpecsDetails(id: string) {
return useQuery<Record<string, any>, Error>(
["playbooks", "settings", "specs", id],
Expand All @@ -28,3 +60,19 @@ export function useGetPlaybookSpecsDetails(id: string) {
}
);
}

export function useSubmitPlaybookRunMutation(
options: Omit<
UseMutationOptions<any, Error, SubmitPlaybookRunFormValues>,
"mutationFn"
> = {}
) {
return useMutation({
mutationFn: async (
input: Omit<SubmitPlaybookRunFormValues, "playbook_spec">
) => {
return submitPlaybookRun(input);
},
...options
});
}
16 changes: 15 additions & 1 deletion src/api/services/playbooks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { SubmitPlaybookRunFormValues } from "../../components/Playbooks/Runs/SubmitPlaybookRunForm";
import {
NewPlaybookSpec,
PlaybookSpec,
UpdatePlaybookSpec
} from "../../components/Playbooks/Settings/PlaybookSpecsTable";
import { AVATAR_INFO } from "../../constants";
import { IncidentCommander } from "../axios";
import { IncidentCommander, PlaybookAPI } from "../axios";
import { GetPlaybooksToRunParams } from "../query-hooks/playbooks";

export async function getAllPlaybooksSpecs() {
const res = await IncidentCommander.get<PlaybookSpec[] | null>(
Expand Down Expand Up @@ -42,3 +44,15 @@ export async function deletePlaybookSpec(id: string) {
);
return res.data;
}

export async function submitPlaybookRun(
input: Omit<SubmitPlaybookRunFormValues, "playbook_spec">
) {
const res = await PlaybookAPI.post("/run", input);
return res.data;
}

export async function getPlaybookRun(params: GetPlaybooksToRunParams) {
const res = await PlaybookAPI.get<PlaybookSpec[] | null>("/list", { params });
return res.data ?? [];
}
30 changes: 30 additions & 0 deletions src/components/Playbooks/Runs/AddPlaybookToRunParams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { useFormikContext } from "formik";
import { SubmitPlaybookRunFormValues } from "./SubmitPlaybookRunForm";
import { useMemo } from "react";
import FormikTextInput from "../../Forms/Formik/FormikTextInput";

export default function AddPlaybookToRunParams() {
const { values } = useFormikContext<SubmitPlaybookRunFormValues>();

const playbookParams = useMemo(
() =>
(values.playbook_spec?.parameters as { name: string; label: string }[]) ??
[],
[values.playbook_spec]
);

return (
<div className="flex flex-col gap-4">
<div className="font-bold">Playbook Run Params</div>
<div className="flex flex-col gap-2">
{playbookParams.length > 0 ? (
playbookParams.map(({ name, label }) => (
<FormikTextInput name={`params.${name}`} label={label} />
))
) : (
<div className="text-gray-400">No parameters for this playbook.</div>
)}
</div>
</div>
);
}
60 changes: 60 additions & 0 deletions src/components/Playbooks/Runs/SelectPlaybookToRun.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { useFormikContext } from "formik";
import { useGetPlaybooksToRun } from "../../../api/query-hooks/playbooks";
import { Loading } from "../../Loading";
import { SubmitPlaybookRunFormValues } from "./SubmitPlaybookRunForm";

type SelectPlaybookToRunProps = {
component_id?: string;
config_id?: string;
check_id?: string;
};

export default function SelectPlaybookToRun({
check_id,
component_id,
config_id
}: SelectPlaybookToRunProps) {
const {
data: playbooks,
isLoading,
error
} = useGetPlaybooksToRun({
check_id,
component_id,
config_id
});

console.log("playbooks", playbooks);

// access formik state here
const { setFieldValue } = useFormikContext<SubmitPlaybookRunFormValues>();

return (
<div className="flex flex-col gap-2">
<div className="font-bold">Select a Playbook</div>
<div className="flex flex-col gap-2">
{error && !playbooks && (
<div className="text-red-500 text-center">{error.message}</div>
)}
{isLoading && !playbooks && <Loading />}
{playbooks && (
<div className="flex flex-col gap-2">
{playbooks?.map(({ id, name, spec }) => (
<div
role="button"
onClick={() => {
// update formik state here
setFieldValue("id", id);
setFieldValue("playbook_spec", spec);
}}
className="flex flex-row rounded-md border border-gray-200 hover:border-gray-400 hover:bg-gray-200 hover:font-semibold cursor py-2 px-2"
>
{name}
</div>
))}
</div>
)}
</div>
</div>
);
}
106 changes: 106 additions & 0 deletions src/components/Playbooks/Runs/SubmitPlaybookRunForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Form, Formik } from "formik";
import SelectPlaybookToRun from "./SelectPlaybookToRun";
import { useMemo } from "react";
import AddPlaybookToRunParams from "./AddPlaybookToRunParams";
import { Modal } from "../../Modal";
import { Button } from "../../Button";
import { useSubmitPlaybookRunMutation } from "../../../api/query-hooks/playbooks";
import { toastError } from "../../Toast/toast";

export type SubmitPlaybookRunFormValues = {
// if this is present in the form, we show step to add params
id: string;
component_id?: string;
config_id?: string;
params?: Record<string, any>;
// do not send this to the backend
playbook_spec?: Record<string, any>;
};

type Props = {
isOpen: boolean;
onClose: () => void;
};

type SubmitPlaybookRunFormForComponentProps = {
type: "component";
componentId: string;
} & Props;

type SubmitPlaybookRunFormForConfigProps = {
type: "config";
configId: string;
} & Props;

type SubmitPlaybookRunFormProps =
| SubmitPlaybookRunFormForComponentProps
| SubmitPlaybookRunFormForConfigProps;

export default function SubmitPlaybookRunForm({
isOpen,
onClose,
...props
}: SubmitPlaybookRunFormProps) {
const initialValues: Partial<SubmitPlaybookRunFormValues> = useMemo(
() => ({
playbook_id: undefined,
params: undefined,
...(props.type === "component"
? { component_id: props.componentId }
: {
config_id: props.configId
})
}),
[props]
);

const { mutate: submitPlaybookRun } = useSubmitPlaybookRunMutation({
onSuccess: () => {
toastError("Playbook run submitted successfully");
onClose();
},
onError: (error) => {
toastError(error.message);
}
});

return (
<Modal
title="Submit a Playbook Run"
open={isOpen}
onClose={onClose}
size="slightly-small"
bodyClass=""
containerClassName=""
>
<Formik
initialValues={initialValues}
onSubmit={(values) => {
const { playbook_spec, ...rest } = values;
submitPlaybookRun(rest as SubmitPlaybookRunFormValues);
}}
>
{({ values, handleSubmit }) => {
return (
<Form onSubmit={handleSubmit} className="flex flex-col gap-2">
<div className="flex p-4 flex-col gap-2">
{!values.id ? (
<SelectPlaybookToRun />
) : (
<AddPlaybookToRunParams />
)}
</div>
<div className="flex flex-row p-4 justify-end bg-gray-200 rounded-b rounded-md">
<Button
disabled={values.id === undefined}
text="Submit"
type="submit"
/>
</div>
</Form>
);
}}
</Formik>
</Modal>
);
}
11 changes: 11 additions & 0 deletions src/components/TopologyCard/TopologyDropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { EvidenceType } from "../../api/services/evidence";
import { TopologyConfigLinkModal } from "../TopologyConfigLinkModal/TopologyConfigLinkModal";
import { useFeatureFlagsContext } from "../../context/FeatureFlagsContext";
import { features } from "../../services/permissions/features";
import SubmitPlaybookRunForm from "../Playbooks/Runs/SubmitPlaybookRunForm";

type TopologyMenuItemProps = {
onClick?: () => void;
Expand Down Expand Up @@ -86,6 +87,7 @@ export const TopologyDropdownMenu = ({
] = useState(false);
const [attachAsAsset, setAttachAsAsset] = useState(false);
const [linkToConfig, setLinkToConfig] = useState(false);
const [submitPlaybookRun, setSubmitPlaybookRun] = useState(false);

return (
<>
Expand Down Expand Up @@ -130,6 +132,8 @@ export const TopologyDropdownMenu = ({
? () => setAttachAsAsset(true)
: label === "Link to config"
? () => setLinkToConfig(true)
: label === "Playbooks"
? () => setSubmitPlaybookRun(true)
: undefined
}
/>
Expand All @@ -154,6 +158,13 @@ export const TopologyDropdownMenu = ({
topology={topology}
/>

<SubmitPlaybookRunForm
isOpen={submitPlaybookRun}
onClose={() => setSubmitPlaybookRun(false)}
type="component"
componentId={topology.id}
/>

{linkToConfig && (
<TopologyConfigLinkModal
onCloseModal={() => setLinkToConfig(false)}
Expand Down
Loading

0 comments on commit 6f16c64

Please sign in to comment.