Skip to content

Commit

Permalink
feat: show approval step in actions list
Browse files Browse the repository at this point in the history
Fixes #2380

Update src/components/Playbooks/Runs/Actions/PlaybookRunsApprovalActionItem.tsx

Co-authored-by: Moshe Immerman <[email protected]>

chore: update icon size
  • Loading branch information
mainawycliffe authored and moshloop committed Oct 31, 2024
1 parent 6270f1b commit 7105c2f
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 17 deletions.
23 changes: 21 additions & 2 deletions src/api/services/playbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PlaybookRunWithActions[] | null>(
// 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<PlaybookRunAction[] | null>(
`/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 ?? [];
Expand Down
11 changes: 10 additions & 1 deletion src/api/types/playbooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand Down
82 changes: 70 additions & 12 deletions src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
PlaybookApproval,
PlaybookRunAction,
PlaybookRunWithActions
} from "@flanksource-ui/api/types/playbooks";
Expand All @@ -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";

Expand All @@ -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);
Expand Down Expand Up @@ -151,41 +165,85 @@ export default function PlaybookRunsActions({
<div className="flex flex-1 flex-col gap-2 overflow-y-auto">
{initializationAction && (
<PlaybookRunsActionItem
isSelected={selectedAction?.id === initializationAction.id}
isSelected={
selectedAction?.type === "Action" &&
selectedAction.data?.id === initializationAction.id
}
key={initializationAction.id}
action={initializationAction}
onClick={() => setSelectedAction(initializationAction)}
onClick={() =>
setSelectedAction({
type: "Action",
data: initializationAction
})
}
stepNumber={0}
/>
)}
{data.playbook_approvals &&
data.playbook_approvals.length === 1 && (
<PlaybookRunsApprovalActionItem
isSelected={selectedAction?.type === "Approval"}
key={data.playbook_approvals[0].id}
approval={data.playbook_approvals[0]}
onClick={() =>
setSelectedAction({
type: "Approval",
data: data.playbook_approvals?.[0]!
})
}
/>
)}
{data.actions.map((action, index) => (
<PlaybookRunsActionItem
isSelected={selectedAction?.id === action.id}
isSelected={
selectedAction?.type === "Action" &&
selectedAction.data?.id === action.id
}
key={action.id}
action={action}
onClick={() => setSelectedAction(action)}
stepNumber={index + 1}
onClick={() =>
setSelectedAction({
data: action,
type: "Action"
})
}
stepNumber={index + (data.playbook_approvals ? 2 : 1)}
/>
))}
</div>
</div>
<div className="flex h-full flex-1 flex-col overflow-hidden bg-gray-700 px-4 py-2 font-mono text-white">
{selectedAction &&
(selectedAction.id === "initialization" ? (
selectedAction.type === "Action" &&
selectedAction.data?.id === "initialization" && (
<div className="flex w-full flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-all">
<PlaybooksRunActionsResults
action={selectedAction}
action={selectedAction.data}
playbook={data.playbooks!}
/>
</div>
) : (
)}

{selectedAction &&
selectedAction.type === "Action" &&
selectedAction.data?.id !== "initialization" && (
<div className="flex w-full flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-all">
<PlaybookRunActionFetch
playbookRunActionId={selectedAction.id}
playbookRunActionId={selectedAction.data?.id!}
playbook={data.playbooks!}
/>
</div>
))}
)}

{selectedAction && selectedAction.type === "Approval" && (
<div className="flex w-full flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden whitespace-pre-wrap break-all">
<PlaybookRunsApprovalActionsResults
approval={selectedAction.data}
playbook={data.playbooks!}
/>
</div>
)}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { PlaybookApproval } from "@flanksource-ui/api/types/playbooks";
import { Avatar } from "@flanksource-ui/ui/Avatar";
import clsx from "clsx";
import { BsCheck2Circle } from "react-icons/bs";

type PlaybookRunsActionItemProps = {
onClick?: () => void;
isSelected?: boolean;
approval: PlaybookApproval;
};

export default function PlaybookRunsApprovalActionItem({
onClick = () => {},
isSelected = false,
approval
}: PlaybookRunsActionItemProps) {
return (
<div
role="button"
className={clsx(
`flex flex-row items-center justify-between rounded border border-gray-200 px-4 py-2 hover:bg-gray-200`,
isSelected ? "bg-gray-200" : "bg-white"
)}
onClick={onClick}
>
<div className="flex flex-col">
<div className="flex flex-row items-center gap-2 text-sm text-gray-600">
<BsCheck2Circle className="text-green-500 h-5 w-auto" />
Approved by{" "}
{approval?.person_id ? (
<Avatar user={approval.person_id} inline showName size="xs" />
) : (
<span className="whitespace-pre-wrap break-all">
{approval.team_id?.name}
</span>
)}
</div>
<div className={`flex flex-row items-center gap-1 text-xs`}></div>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -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<PlaybookSpec, "name">;
};

export default function PlaybookRunsApprovalActionsResults({
approval,
className = "whitespace-pre-wrap break-all",
playbook
}: Props) {
if (!approval) {
return null;
}

return (
<div className="relative flex h-full w-full flex-col">
<p className="space-x-2">
Approved by{" "}
{approval.person_id ? (
<Avatar user={approval.person_id} inline showName />
) : (
<span className={className}>{approval.team_id?.name}</span>
)}
</p>
</div>
);
}
13 changes: 11 additions & 2 deletions src/ui/Avatar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { Tooltip } from "react-tooltip";
import { User } from "../../api/types/users";

interface IProps {
size?: "sm" | "lg" | "md";
size?: "sm" | "lg" | "md" | "xs";
circular?: boolean;
inline?: boolean;
alt?: string;
user?: Partial<User>;
imageProps?: React.ComponentPropsWithoutRef<"img">;
containerProps?: React.ComponentPropsWithoutRef<"div">;
unload?: boolean;
showName?: boolean;
}

export function Avatar({
Expand All @@ -24,9 +25,14 @@ export function Avatar({
containerProps,
imageProps,
inline = false,
circular = true
circular = true,
showName = false
}: IProps) {
const [textSize, setTextSize] = useState(() => {
if (size === "xs") {
return "12px";
}

if (size !== "sm") {
return "16px";
}
Expand All @@ -41,6 +47,8 @@ export function Avatar({
});
const sizeClass = useMemo(() => {
switch (size) {
case "xs":
return "w-5 h-5 text-xs";
case "sm":
return "w-6 h-6 text-xs";
case "lg":
Expand Down Expand Up @@ -123,6 +131,7 @@ export function Avatar({
<BsFillPersonFill className="text-warmer-gray" />
)}
</div>
{showName && <span>{user?.name}</span>}
<Tooltip id="user-name" />
</>
);
Expand Down

0 comments on commit 7105c2f

Please sign in to comment.