From 8531ed3408c3edcf493081b37811ce4b000c66cf Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Tue, 29 Oct 2024 13:18:15 +0300 Subject: [PATCH] feat: show approval step in actions list Fixes #2380 --- src/api/services/playbooks.ts | 23 +++++- src/api/types/playbooks.ts | 11 ++- .../Runs/Actions/PlaybookRunsActions.tsx | 81 ++++++++++++++++--- .../PlaybookRunsApprovalActionItem.tsx | 33 ++++++++ .../PlaybookRunsApprovalActionsResults.tsx | 34 ++++++++ src/ui/Avatar/index.tsx | 5 +- 6 files changed, 171 insertions(+), 16 deletions(-) create mode 100644 src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionItem.tsx create mode 100644 src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionsResults.tsx diff --git a/src/api/services/playbooks.ts b/src/api/services/playbooks.ts index c4ff791e2..aa815cb49 100644 --- a/src/api/services/playbooks.ts +++ b/src/api/services/playbooks.ts @@ -92,13 +92,32 @@ export async function getPlaybookToRunForResource( } export async function getPlaybookRun(id: string) { + const select = [ + "*", + `created_by(${AVATAR_INFO})`, + "playbooks(id,name,title,spec)", + "component:components(id,name,icon)", + "config:config_items(id,name,type,config_class)", + "check:checks(id,name,icon)", + `playbook_approvals(*, person_id(${AVATAR_INFO}), team_id(*))` + ].join(","); + const res = await IncidentCommander.get( // todo: use playbook names instead - `/playbook_runs?id=eq.${id}&select=*,created_by(${AVATAR_INFO}),playbooks(id,name,title,spec),component:components(id,name,icon),config:config_items(id,name,type,config_class),check:checks(id,name,icon)` + `/playbook_runs?id=eq.${id}&select=${select}` ); + const actionsSelect = [ + "id", + "name", + "status", + "start_time", + "end_time", + "scheduled_time" + ].join(","); + const resActions = await IncidentCommander.get( - `/playbook_run_actions?select=id,name,status,start_time,end_time,scheduled_time&order=start_time.asc&playbook_run_id=eq.${id}` + `/playbook_run_actions?select=${actionsSelect}&order=start_time.asc&playbook_run_id=eq.${id}` ); const actions = resActions.data ?? []; diff --git a/src/api/types/playbooks.ts b/src/api/types/playbooks.ts index 98c3aa763..4340fd640 100644 --- a/src/api/types/playbooks.ts +++ b/src/api/types/playbooks.ts @@ -3,7 +3,7 @@ import { Agent, Avatar, CreatedAt } from "../traits"; import { ConfigItem } from "./configs"; import { HealthCheckSummary } from "./health"; import { Topology } from "./topology"; -import { User } from "./users"; +import { Team, User } from "./users"; export type PlaybookRunStatus = | "scheduled" @@ -40,8 +40,17 @@ export type PlaybookRunAction = { error?: string; }; +export type PlaybookApproval = { + id: string; + run_id: string; + person_id?: User; + team_id?: Team; + created_at: string; +}; + export interface PlaybookRunWithActions extends PlaybookRun { actions: PlaybookRunAction[]; + playbook_approvals?: PlaybookApproval[]; } export interface PlaybookRun extends CreatedAt, Avatar, Agent { diff --git a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx index 01f4e7c40..8b7b51219 100644 --- a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx +++ b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx @@ -1,4 +1,5 @@ import { + PlaybookApproval, PlaybookRunAction, PlaybookRunWithActions } from "@flanksource-ui/api/types/playbooks"; @@ -16,6 +17,8 @@ import ReRunPlaybookWithParamsButton from "../Submit/ReRunPlaybookWithParamsButt import { PlaybookStatusDescription } from "./../PlaybookRunsStatus"; import PlaybookRunActionFetch from "./PlaybookRunActionFetch"; import PlaybookRunsActionItem from "./PlaybookRunsActionItem"; +import PlaybookRunsApprovalActionItem from "./PlaybookRunsApprovalActionItem"; +import PlaybookRunsApprovalActionsResults from "./PlaybookRunsApprovalActionsResults"; import PlaybooksRunActionsResults from "./PlaybooksActionsResults"; import ShowPlaybookRunsParams from "./ShowParamaters/ShowPlaybookRunsParams"; @@ -29,10 +32,21 @@ export default function PlaybookRunsActions({ refetch = () => {} }: PlaybookRunActionsProps) { const [selectedAction, setSelectedAction] = useState< - PlaybookRunAction | undefined + | { + type: "Action"; + data?: PlaybookRunAction; + } + | { + type: "Approval"; + data: PlaybookApproval; + } + | undefined >(() => { // show the last action by default - return data.actions.at(-1); + return { + type: "Action", + data: data.actions.at(-1) + }; }); const resource = getResourceForRun(data); @@ -151,41 +165,84 @@ export default function PlaybookRunsActions({
{initializationAction && ( setSelectedAction(initializationAction)} + onClick={() => + setSelectedAction({ + type: "Action", + data: initializationAction + }) + } stepNumber={0} /> )} + {data.playbook_approvals && ( + + setSelectedAction({ + type: "Approval", + data: data.playbook_approvals?.[0]! + }) + } + /> + )} {data.actions.map((action, index) => ( setSelectedAction(action)} - stepNumber={index + 1} + onClick={() => + setSelectedAction({ + data: action, + type: "Action" + }) + } + stepNumber={index + (data.playbook_approvals ? 2 : 1)} /> ))}
{selectedAction && - (selectedAction.id === "initialization" ? ( + selectedAction.type === "Action" && + selectedAction.data?.id === "initialization" && (
- ) : ( + )} + + {selectedAction && + selectedAction.type === "Action" && + selectedAction.data?.id !== "initialization" && (
- ))} + )} + + {selectedAction && selectedAction.type === "Approval" && ( +
+ +
+ )}
diff --git a/src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionItem.tsx b/src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionItem.tsx new file mode 100644 index 000000000..3221ff477 --- /dev/null +++ b/src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionItem.tsx @@ -0,0 +1,33 @@ +import clsx from "clsx"; +import { FaCheckCircle } from "react-icons/fa"; + +type PlaybookRunsActionItemProps = { + onClick?: () => void; + isSelected?: boolean; + stepNumber: number; +}; + +export default function PlaybookRunsApprovalActionItem({ + onClick = () => {}, + isSelected = false, + stepNumber +}: PlaybookRunsActionItemProps) { + return ( +
+
+
+ + Approval +
+
+
+
+ ); +} diff --git a/src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionsResults.tsx b/src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionsResults.tsx new file mode 100644 index 000000000..845b7185f --- /dev/null +++ b/src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionsResults.tsx @@ -0,0 +1,34 @@ +import { + PlaybookApproval, + PlaybookSpec +} from "@flanksource-ui/api/types/playbooks"; +import { Avatar } from "@flanksource-ui/ui/Avatar"; + +type Props = { + approval?: PlaybookApproval; + className?: string; + playbook: Pick; +}; + +export default function PlaybookRunsApprovalActionsResults({ + approval, + className = "whitespace-pre-wrap break-all", + playbook +}: Props) { + if (!approval) { + return null; + } + + return ( +
+

+ Approved by{" "} + {approval.person_id ? ( + + ) : ( + {approval.team_id?.name} + )} +

+
+ ); +} diff --git a/src/ui/Avatar/index.tsx b/src/ui/Avatar/index.tsx index b8d46127d..b061b7df7 100644 --- a/src/ui/Avatar/index.tsx +++ b/src/ui/Avatar/index.tsx @@ -14,6 +14,7 @@ interface IProps { imageProps?: React.ComponentPropsWithoutRef<"img">; containerProps?: React.ComponentPropsWithoutRef<"div">; unload?: boolean; + showName?: boolean; } export function Avatar({ @@ -24,7 +25,8 @@ export function Avatar({ containerProps, imageProps, inline = false, - circular = true + circular = true, + showName = false }: IProps) { const [textSize, setTextSize] = useState(() => { if (size !== "sm") { @@ -123,6 +125,7 @@ export function Avatar({ )} + {showName && {user?.name}} );