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

1445 playbook improvements #1455

Merged
merged 2 commits into from
Oct 24, 2023
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
14 changes: 9 additions & 5 deletions src/api/query-hooks/playbooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import {
useMutation,
useQuery
} from "@tanstack/react-query";
import { SubmitPlaybookRunFormValues } from "../../components/Playbooks/Runs/Submit/SubmitPlaybookRunForm";
import { PlaybookSpec } from "../../components/Playbooks/Settings/PlaybookSpecsTable";
import {
getAllPlaybooksSpecs,
getPlaybookToRunForResource,
getPlaybookSpec,
getPlaybookToRunForResource,
submitPlaybookRun
} from "../services/playbooks";
import { SubmitPlaybookRunFormValues } from "../../components/Playbooks/Runs/SubmitPlaybookRunForm";

export function useGetAllPlaybookSpecs(
options: UseQueryOptions<PlaybookSpec[], Error> = {}
Expand Down Expand Up @@ -48,15 +48,19 @@ export function useGetPlaybooksToRun(
);
}

export function useGetPlaybookSpecsDetails(id: string) {
return useQuery<Record<string, any>, Error>(
export function useGetPlaybookSpecsDetails(
id: string,
options: UseQueryOptions<PlaybookSpec | undefined, Error> = {}
) {
return useQuery<PlaybookSpec | undefined, Error>(
["playbooks", "settings", "specs", id],
async () => getPlaybookSpec(id),
{
enabled: !!id,
cacheTime: 0,
staleTime: 0,
keepPreviousData: false
keepPreviousData: false,
...options
}
);
}
Expand Down
19 changes: 13 additions & 6 deletions src/api/services/playbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
PlaybookRun,
PlaybookRunAction
} from "../../components/Playbooks/Runs/PlaybookRunTypes";
import { SubmitPlaybookRunFormValues } from "../../components/Playbooks/Runs/SubmitPlaybookRunForm";
import { SubmitPlaybookRunFormValues } from "../../components/Playbooks/Runs/Submit/SubmitPlaybookRunForm";
import {
NewPlaybookSpec,
PlaybookSpec,
Expand All @@ -22,10 +22,10 @@ export async function getAllPlaybooksSpecs() {
}

export async function getPlaybookSpec(id: string) {
const res = await IncidentCommander.get<PlaybookSpec | null>(
`/playbooks/${id},created_by(${AVATAR_INFO})`
const res = await IncidentCommander.get<PlaybookSpec[] | null>(
`/playbooks?id=eq.${id}&select=*,created_by(${AVATAR_INFO})`
);
return res.data ?? undefined;
return res.data?.[0] ?? undefined;
}

export async function createPlaybookSpec(spec: NewPlaybookSpec) {
Expand Down Expand Up @@ -89,23 +89,30 @@ export async function getPlaybookRuns({
componentId,
configId,
pageIndex,
pageSize
pageSize,
playbookId
}: {
componentId?: string;
configId?: string;
pageIndex: number;
pageSize: number;
playbookId?: string;
}) {
const componentParamString = componentId
? `&component_id=eq.${componentId}`
: "";

const configParamString = configId ? `&config_id=eq.${configId}` : "";

const pagingParams = `&limit=${pageSize}&offset=${pageIndex * pageSize}`;

const playbookParamsString = playbookId
? `&playbook_id=eq.${playbookId}`
: "";

const res = await resolve(
ConfigDB.get<PlaybookRun[] | null>(
`/playbook_runs?select=*,playbooks(id,name),component:components(id,name,icon),check:checks(id,name,icon),config:config_items(id,name,type,config_class)&order=created_at.desc${componentParamString}&${configParamString}${pagingParams}}`,
`/playbook_runs?select=*,playbooks(id,name),component:components(id,name,icon),check:checks(id,name,icon),config:config_items(id,name,type,config_class)&&order=created_at.desc${playbookParamsString}${componentParamString}&${configParamString}${pagingParams}}`,
{
headers: {
Prefer: "count=exact"
Expand Down
2 changes: 1 addition & 1 deletion src/components/Canary/minimal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { HealthCheck } from "../../types/healthChecks";
import AttachAsEvidenceButton from "../AttachEvidenceDialog/AttachAsEvidenceDialogButton";
import { timeRanges } from "../Dropdown/TimeRange";
import { Modal } from "../Modal";
import SelectPlaybookToRun from "../Playbooks/Runs/SelectPlaybookToRun";
import SelectPlaybookToRun from "../Playbooks/Runs/Submit/SelectPlaybookToRun";
import { toastError } from "../Toast/toast";
import { CheckDetails } from "./CanaryPopup/CheckDetails";
import { CheckTitle } from "./CanaryPopup/CheckTitle";
Expand Down
2 changes: 1 addition & 1 deletion src/components/ConfigSidebar/ConfigActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { EvidenceType } from "../../api/services/evidence";
import { usePartialUpdateSearchParams } from "../../hooks/usePartialUpdateSearchParams";
import { ActionLink } from "../ActionLink/ActionLink";
import AttachAsEvidenceButton from "../AttachEvidenceDialog/AttachAsEvidenceDialogButton";
import SelectPlaybookToRun from "../Playbooks/Runs/SelectPlaybookToRun";
import SelectPlaybookToRun from "../Playbooks/Runs/Submit/SelectPlaybookToRun";

type ConfigActionBarProps = {
configId: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMemo } from "react";
import FormikTextInput from "../../Forms/Formik/FormikTextInput";
import { PlaybookSpec } from "../Settings/PlaybookSpecsTable";
import FormikTextInput from "../../../Forms/Formik/FormikTextInput";
import { PlaybookSpec } from "../../Settings/PlaybookSpecsTable";

type AddPlaybookToRunParamsProps = {
playbookSpec: PlaybookSpec;
Expand All @@ -21,7 +21,11 @@ export default function AddPlaybookToRunParams({
<div className="flex flex-col gap-2">
{playbookParams.length > 0 ? (
playbookParams.map(({ name, label }) => (
<FormikTextInput name={`params.${name}`} label={label} />
<FormikTextInput
name={`params.${name}`}
label={label}
key={`${label}-${name}`}
/>
))
) : (
<div className="text-gray-400">No parameters for this playbook.</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Float } from "@headlessui-float/react";
import { Fragment, useState } from "react";
import { FaChevronDown, FaChevronUp } from "react-icons/fa";
import { useGetPlaybooksToRun } from "../../../api/query-hooks/playbooks";
import { Button } from "../../Button";
import { useGetPlaybooksToRun } from "../../../../api/query-hooks/playbooks";
import { Button } from "../../../Button";
import { Menu } from "@headlessui/react";
import { PlaybookSpec } from "../Settings/PlaybookSpecsTable";
import { PlaybookSpec } from "../../Settings/PlaybookSpecsTable";
import SubmitPlaybookRunForm from "./SubmitPlaybookRunForm";

type SelectPlaybookToRunProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Form, Formik } from "formik";
import { useMemo } from "react";
import { useSubmitPlaybookRunMutation } from "../../../api/query-hooks/playbooks";
import { Button } from "../../Button";
import { Modal } from "../../Modal";
import { toastError, toastSuccess } from "../../Toast/toast";
import { PlaybookSpec } from "../Settings/PlaybookSpecsTable";
import { useSubmitPlaybookRunMutation } from "../../../../api/query-hooks/playbooks";
import { Button } from "../../../Button";
import { Modal } from "../../../Modal";
import { toastError, toastSuccess } from "../../../Toast/toast";
import { PlaybookSpec } from "../../Settings/PlaybookSpecsTable";
import AddPlaybookToRunParams from "./AddPlaybookToRunParams";

export type SubmitPlaybookRunFormValues = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { PlaybookSpec } from "../../../Settings/PlaybookSpecsTable";
import SelectPlaybookToRun from "./../SelectPlaybookToRun";

const playbooks: PlaybookSpec[] = [
{
id: "1",
name: "Playbook 1",
created_at: "2021-09-01T00:00:00Z",
source: "UI",
spec: {},
updated_at: "2021-09-01T00:00:00Z"
},
{
id: "2",
name: "Playbook 2",
created_at: "2021-09-01T00:00:00Z",
source: "UI",
spec: {},
updated_at: "2021-09-01T00:00:00Z"
}
];

global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn()
}));

// Define a mock server to handle PATCH requests
const server = setupServer(
rest.get("/api/playbook/list", (req, res, ctx) => {
return res(ctx.json(playbooks));
})
);

const queryClient = new QueryClient();

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe("SelectPlaybookToRun", () => {
it("should render dropdown list with playbooks", async () => {
render(
<QueryClientProvider client={queryClient}>
<SelectPlaybookToRun component_id="component_id" />
</QueryClientProvider>
);

const playbooksButton = await screen.findByRole("button", {
name: /playbooks/i
});

userEvent.click(playbooksButton);

expect(await screen.findByText(/playbook 1/i)).toBeInTheDocument();
expect(await screen.findByText(/playbook 2/i)).toBeInTheDocument();
});

it("should open runs page, when you click a playbook item", async () => {
render(
<QueryClientProvider client={queryClient}>
<SelectPlaybookToRun check_id="check_id" />
</QueryClientProvider>
);

const playbooksButton = await screen.findByRole("button", {
name: /playbooks/i
});

userEvent.click(playbooksButton);

const playbook1 = await screen.findByText(/playbook 1/i);

userEvent.click(playbook1);

await waitFor(() => {
expect(
screen.getByRole("heading", { level: 1, name: /playbook 1/i })
).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { rest } from "msw";
import { setupServer } from "msw/node";
import { PlaybookSpec } from "../../../Settings/PlaybookSpecsTable";
import SubmitPlaybookRunForm from "./../SubmitPlaybookRunForm";

const playbookSpec: PlaybookSpec = {
id: "1",
name: "Playbook 1",
source: "UI",
spec: {
parameters: [
{
label: "Label",
name: "name"
}
]
},
created_at: "2021-09-01T00:00:00Z",
updated_at: "2021-09-01T00:00:00Z"
};

global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn()
}));

// Define a mock server to handle PATCH requests
const server = setupServer(
rest.post("/api/playbook/run", (req, res, ctx) => {
return res(ctx.json(playbookSpec));
})
);

const queryClient = new QueryClient();

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe("SubmitPlaybookRunForm", () => {
const componentId = "component-1";
const checkId = "check-1";
const configId = "config-1";

it("should render the form with the correct initial values", async () => {
const closeFn = jest.fn();

render(
<QueryClientProvider client={queryClient}>
<SubmitPlaybookRunForm
isOpen={true}
onClose={closeFn}
playbookSpec={playbookSpec}
componentId={componentId}
checkId={checkId}
configId={configId}
/>
</QueryClientProvider>
);

expect(
await screen.findByRole("heading", { level: 1, name: /Playbook 1/i })
).toBeInTheDocument();

expect(screen.getByLabelText("Label")).toBeInTheDocument();

expect(screen.getByRole("button", { name: /Submit/i })).toBeInTheDocument();

userEvent.click(screen.getByRole("button", { name: /close/i }));

await waitFor(() => {
expect(closeFn).toHaveBeenCalled();
});
});

it("should submit the form when the submit button is clicked", async () => {
const closeFn = jest.fn();
render(
<QueryClientProvider client={queryClient}>
<SubmitPlaybookRunForm
isOpen={true}
onClose={closeFn}
playbookSpec={playbookSpec}
componentId={componentId}
checkId={checkId}
configId={configId}
/>
</QueryClientProvider>
);

expect(
await screen.findByRole("heading", { level: 1, name: /Playbook 1/i })
).toBeInTheDocument();

const input = screen.getByLabelText("Label");

fireEvent.change(input, { target: { value: "test" } });

const btn = screen.getByRole("button", { name: /Submit/i });

userEvent.click(btn);

await waitFor(() => {
expect(closeFn).toHaveBeenCalledTimes(1);
});
});
});
2 changes: 1 addition & 1 deletion src/components/TopologySidebar/TopologyActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Topology } from "../../context/TopologyPageContext";
import { features } from "../../services/permissions/features";
import { ActionLink } from "../ActionLink/ActionLink";
import { AttachEvidenceDialog } from "../AttachEvidenceDialog";
import SelectPlaybookToRun from "../Playbooks/Runs/SelectPlaybookToRun";
import SelectPlaybookToRun from "../Playbooks/Runs/Submit/SelectPlaybookToRun";
import TopologySnapshotModal from "../TopologyCard/TopologySnapshotModal";
import { TopologyConfigLinkModal } from "../TopologyConfigLinkModal/TopologyConfigLinkModal";

Expand Down
Loading
Loading