Skip to content

Commit

Permalink
feat: add dropdown for components to submit playbook runs
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 21, 2023
1 parent 7938b56 commit 367a9b6
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 14 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
});
}
22 changes: 21 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,21 @@ 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 paramsString = Object.entries(params)
.filter(([, value]) => value)
.map(([key, value]) => `${key}=${value}`)
.join("&");
const res = await PlaybookAPI.get<PlaybookSpec[] | null>(
`/list?${paramsString}`
);
return res.data ?? [];
}
3 changes: 2 additions & 1 deletion src/components/Menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,6 @@ const MenuC = ({ children }: MenuProps) => (
export const Menu = Object.assign(MenuC, {
Item: Item,
Items: Items,
VerticalIconButton: VerticalIconButton
VerticalIconButton: VerticalIconButton,
Button: HLMenu.Button
});
32 changes: 32 additions & 0 deletions src/components/Playbooks/Runs/AddPlaybookToRunParams.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useMemo } from "react";
import FormikTextInput from "../../Forms/Formik/FormikTextInput";
import { PlaybookSpec } from "../Settings/PlaybookSpecsTable";

type AddPlaybookToRunParamsProps = {
playbookSpec: PlaybookSpec;
};

export default function AddPlaybookToRunParams({
playbookSpec
}: AddPlaybookToRunParamsProps) {
const playbookParams = useMemo(
() =>
(playbookSpec.spec.parameters as { name: string; label: string }[]) ?? [],
[playbookSpec]
);

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>
);
}
79 changes: 79 additions & 0 deletions src/components/Playbooks/Runs/SelectPlaybookToRun.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Fragment, useState } from "react";
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
import { useGetPlaybooksToRun } from "../../../api/query-hooks/playbooks";
import { Button } from "../../Button";
import SubmitPlaybookRunForm from "./SubmitPlaybookRunForm";
import { Menu } from "../../Menu";
import { PlaybookSpec } from "../Settings/PlaybookSpecsTable";

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

export default function SelectPlaybookToRun({
check_id,
component_id,
config_id
}: SelectPlaybookToRunProps) {
const [selectedPlaybookSpec, setSelectedPlaybookSpec] =
useState<PlaybookSpec>();

const {
data: playbooks,
isLoading,
error
} = useGetPlaybooksToRun({
check_id,
component_id,
config_id
});

if (error || playbooks?.length === 0 || isLoading) {
return null;
}

return (
<div className="flex flex-col items-center p-1 relative">
<Menu>
<Menu.Button as={Fragment}>
{({ open }) => (
<div className="flex items-center">
<Button
text={
<div className="flex flex-row gap-2">
<span>Playbooks</span>
{!open ? <FaChevronDown /> : <FaChevronUp />}
</div>
}
className="text-sm btn-white"
disabled={isLoading}
/>
</div>
)}
</Menu.Button>
<Menu.Items>
{playbooks?.map((playbook) => (
<Menu.Item
as="button"
onClick={() => setSelectedPlaybookSpec(playbook)}
key={playbook.id}
>
{playbook.name}
</Menu.Item>
))}
</Menu.Items>
</Menu>
{selectedPlaybookSpec && (
<SubmitPlaybookRunForm
type="component"
componentId={component_id!}
isOpen={!!selectedPlaybookSpec}
onClose={() => setSelectedPlaybookSpec(undefined)}
playbookSpec={selectedPlaybookSpec}
/>
)}
</div>
);
}
116 changes: 116 additions & 0 deletions src/components/Playbooks/Runs/SubmitPlaybookRunForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
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";
import { PlaybookSpec } from "../Settings/PlaybookSpecsTable";

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;
playbookSpec: PlaybookSpec;
};

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

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

type SubmitPlaybookRunFormForCheckProps = {
type: "check";
checkId: string;
} & Props;

type SubmitPlaybookRunFormProps =
| SubmitPlaybookRunFormForComponentProps
| SubmitPlaybookRunFormForConfigProps
| SubmitPlaybookRunFormForCheckProps;

export default function SubmitPlaybookRunForm({
isOpen,
onClose,
playbookSpec,
...props
}: SubmitPlaybookRunFormProps) {
const initialValues: Partial<SubmitPlaybookRunFormValues> = useMemo(
() => ({
playbook_id: playbookSpec.id,
params: undefined,

...(props.type === "component"
? { component_id: props.componentId }
: props.type === "check"
? {
check_id: props.checkId
}
: {
config_id: props.configId
})
}),
[props, playbookSpec]
);

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">
<AddPlaybookToRunParams playbookSpec={playbookSpec} />
</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>
);
}
12 changes: 6 additions & 6 deletions src/components/TopologyCard/TopologyDropdownMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Menu } from "../Menu";
import { Topology } from "../../context/TopologyPageContext";
import { topologyActionItems } from "../TopologySidebar/TopologyActionBar";
import { CSSProperties, useCallback, useMemo, useState } from "react";
import { AttachEvidenceDialog } from "../AttachEvidenceDialog";
import TopologySnapshotModal from "./TopologySnapshotModal";
import { EvidenceType } from "../../api/services/evidence";
import { TopologyConfigLinkModal } from "../TopologyConfigLinkModal/TopologyConfigLinkModal";
import { useFeatureFlagsContext } from "../../context/FeatureFlagsContext";
import { Topology } from "../../context/TopologyPageContext";
import { features } from "../../services/permissions/features";
import { AttachEvidenceDialog } from "../AttachEvidenceDialog";
import { Menu } from "../Menu";
import { TopologyConfigLinkModal } from "../TopologyConfigLinkModal/TopologyConfigLinkModal";
import { topologyActionItems } from "../TopologySidebar/TopologyActionBar";
import TopologySnapshotModal from "./TopologySnapshotModal";

type TopologyMenuItemProps = {
onClick?: () => void;
Expand Down
Loading

0 comments on commit 367a9b6

Please sign in to comment.