Skip to content

Commit

Permalink
Merge pull request #1358 from flanksource/1353-components-playbooks
Browse files Browse the repository at this point in the history
1353-components-playbooks
  • Loading branch information
moshloop authored Oct 10, 2023
2 parents 2e5fb17 + 36d7344 commit 48e5a34
Show file tree
Hide file tree
Showing 19 changed files with 601 additions and 63 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
});
}
45 changes: 43 additions & 2 deletions src/api/services/playbooks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { PlaybookRunAction } from "../../components/Playbooks/Runs/PlaybookRunsSidePanel";
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 { ConfigDB, IncidentCommander, PlaybookAPI } from "../axios";
import { GetPlaybooksToRunParams } from "../query-hooks/playbooks";
import { resolve } from "../resolve";

export async function getAllPlaybooksSpecs() {
const res = await IncidentCommander.get<PlaybookSpec[] | null>(
Expand All @@ -27,7 +31,7 @@ export async function createPlaybookSpec(spec: NewPlaybookSpec) {

export async function updatePlaybookSpec(spec: UpdatePlaybookSpec) {
const res = await IncidentCommander.patch<PlaybookSpec>(
`/playbooks?id=eq.${spec.id}`,
`/playbooks?id=eq.${spec.ID}`,
spec
);
return res.data;
Expand All @@ -42,3 +46,40 @@ 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 ?? [];
}

export async function getPlaybookRuns(componentId?: string, configId?: string) {
const componentParamString = componentId
? `&component_id=eq.${componentId}`
: "";
const configParamString = configId ? `&config_id=eq.${configId}` : "";

const res = await resolve(
ConfigDB.get<PlaybookRunAction[] | null>(
`/playbook_runs?select=*,playbooks(id,name)&order=created_at.desc${componentParamString}&${configParamString}`,
{
headers: {
Prefer: "count=exact"
}
}
)
);
return res;
}
35 changes: 21 additions & 14 deletions src/components/Canary/minimal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +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 { toastError } from "../Toast/toast";
import { CheckDetails } from "./CanaryPopup/CheckDetails";
import { CheckTitle } from "./CanaryPopup/CheckTitle";
Expand Down Expand Up @@ -120,24 +121,30 @@ const MinimalCanaryFC = ({
timeRange={timeRange}
className={`flex flex-col overflow-y-auto flex-1`}
/>
<div className="rounded-t-none flex space-x-2 bg-gray-100 px-8 py-4 justify-end absolute w-full bottom-0 left-0">
<div className="rounded-t-none flex gap-2 bg-gray-100 px-8 py-4 justify-end absolute w-full bottom-0 left-0">
{selectedCheck?.canary_id && (
<HealthCheckEdit check={selectedCheck as HealthCheck} />
)}
{!isCanaryUI && (
<AttachAsEvidenceButton
check_id={selectedCheck?.id}
evidence={{
check_id: selectedCheck?.id,
includeMessages: true,
start: timeRange
}}
type={EvidenceType.Check}
callback={(success: boolean) => {
console.log(success);
}}
className="btn-primary float-right"
/>
<>
<SelectPlaybookToRun
className="btn-primary"
check_id={selectedCheck?.id}
/>
<AttachAsEvidenceButton
check_id={selectedCheck?.id}
evidence={{
check_id: selectedCheck?.id,
includeMessages: true,
start: timeRange
}}
type={EvidenceType.Check}
callback={(success: boolean) => {
console.log(success);
}}
className="btn-primary"
/>
</>
)}
</div>
</div>
Expand Down
2 changes: 2 additions & 0 deletions src/components/ConfigSidebar/ConfigActionBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +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";

type ConfigActionBarProps = {
configId: string;
Expand Down Expand Up @@ -78,6 +79,7 @@ export default function ConfigActionBar({
setChecked({});
}}
/>
<SelectPlaybookToRun config_id={configId} />
</div>
);
}
12 changes: 11 additions & 1 deletion src/components/ConfigSidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import SlidingSideBar from "../SlidingSideBar";
import { ConfigDetails } from "./ConfigDetails";
import ConfigActionBar from "./ConfigActionBar";
import { useCallback, useState } from "react";
import { PlaybookRunsSidePanel } from "../Playbooks/Runs/PlaybookRunsSidePanel";

type SidePanels =
| "ConfigDetails"
| "Configs"
| "Incidents"
| "Costs"
| "ConfigChanges"
| "Insights";
| "Insights"
| "PlaybookRuns";

export default function ConfigSidebar() {
const [openedPanel, setOpenedPanel] = useState<SidePanels | undefined>(
Expand Down Expand Up @@ -70,6 +72,14 @@ export default function ConfigSidebar() {
panelCollapsedStatusChange(status, "Costs")
}
/>
<PlaybookRunsSidePanel
panelType="config"
configId={id}
isCollapsed={openedPanel !== "PlaybookRuns"}
onCollapsedStateChange={(status) =>
panelCollapsedStatusChange(status, "PlaybookRuns")
}
/>
<ConfigChanges
configID={id}
isCollapsed={openedPanel !== "ConfigChanges"}
Expand Down
39 changes: 18 additions & 21 deletions src/components/InfiniteTable/InfiniteTable.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {
useReactTable,
getCoreRowModel,
getSortedRowModel,
ColumnDef,
Row,
flexRender
flexRender,
getCoreRowModel,
getSortedRowModel,
useReactTable
} from "@tanstack/react-table";
import React, { useCallback, useMemo, useRef } from "react";
import { useVirtualizer } from "@tanstack/react-virtual";
import clsx from "clsx";
import React, { useCallback, useMemo, useRef } from "react";

type InfiniteTableProps<T> = React.HTMLProps<HTMLDivElement> & {
columns: ColumnDef<T, any>[];
Expand All @@ -22,6 +22,7 @@ type InfiniteTableProps<T> = React.HTMLProps<HTMLDivElement> & {
loaderView: React.ReactNode;
stickyHead?: boolean;
columnsClassName?: { [key: string]: string };
onRowClick?: (row: Row<T>) => void;
};

export function InfiniteTable<T>({
Expand All @@ -35,7 +36,8 @@ export function InfiniteTable<T>({
loaderView,
stickyHead,
columnsClassName,
virtualizedRowEstimatedHeight = 50
virtualizedRowEstimatedHeight = 50,
onRowClick = () => {}
}: InfiniteTableProps<T>) {
const tableContainerRef = useRef<HTMLDivElement>(null);
const containerStyle = useMemo(() => {
Expand Down Expand Up @@ -101,25 +103,17 @@ export function InfiniteTable<T>({
style={containerStyle}
>
<table
className={clsx(
`table-auto table-fixed w-full flex flex-col p-0`,
stickyHead && "relative"
)}
className={clsx(`table-auto table-fixed p-0`, stickyHead && "relative")}
>
<thead
className={`flex flex-col flex-1 bg-white ${
stickyHead ? "sticky top-0 z-1" : ""
}`}
>
<thead className={`bg-white ${stickyHead ? "sticky top-0 z-1" : ""}`}>
{table.getHeaderGroups().map((headerGroup) => (
<tr className="flex flex-row flex-1" key={headerGroup.id}>
<tr className="" key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
colSpan={header.colSpan}
style={{ padding: "0px" }}
className={clsx(columnsClassName?.[header.id])}
className={clsx(columnsClassName?.[header.id], "py-0")}
>
{header.isPlaceholder ? null : (
<div
Expand All @@ -142,7 +136,7 @@ export function InfiniteTable<T>({
</tr>
))}
</thead>
<tbody className={`flex flex-col flex-1`}>
<tbody>
{paddingTop > 0 && (
<tr>
<td style={{ height: `${paddingTop}px` }} />
Expand All @@ -151,12 +145,15 @@ export function InfiniteTable<T>({
{virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index] as Row<T>;
return (
<tr className="flex flex-row flex-1" key={row.id}>
<tr role="button" onClick={() => onRowClick(row)} key={row.id}>
{row.getVisibleCells().map((cell) => {
return (
<td
key={cell.id}
className={clsx(columnsClassName?.[cell.column.id])}
className={clsx(
columnsClassName?.[cell.column.id],
"py-1"
)}
>
{flexRender(
cell.column.columnDef.cell,
Expand Down
4 changes: 2 additions & 2 deletions src/components/Insights/Insights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ export default function InsightsDetails(
stickyHead
virtualizedRowEstimatedHeight={40}
columnsClassName={{
change_type: "flex-1 w-[100px]",
first_observed: "text-right"
change_type: "",
first_observed: "fit-content whitespace-nowrap"
}}
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Insights/cells/ConfigInsightAgeCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ export default function ConfigInsightAgeCell({
unknown
>) {
const age = relativeDateTime(row.original.first_observed);
return <span className="flex flex-nowrap py-1 text-sm">{age}</span>;
return <span className="flex whitespace-nowrap py-1 text-sm">{age}</span>;
}
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
});
Loading

0 comments on commit 48e5a34

Please sign in to comment.