From c860af9c5d7de4a3c138228b0ff5946cb7b35002 Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Fri, 11 Oct 2024 23:11:58 +0300 Subject: [PATCH 1/3] feat: add playbook approval feature on the UI Fixes #2304 --- src/api/services/playbooks.ts | 7 +++ src/api/types/playbooks.ts | 3 +- .../Runs/Actions/PlaybookRunsActions.tsx | 16 ++++++- .../Runs/ApprovePlaybookRunModal.tsx | 48 +++++++++++++++++++ .../Runs/Filter/PlaybookStatusDropdown.tsx | 5 ++ .../Playbooks/Runs/PlaybookRunsList.tsx | 14 +++++- .../Playbooks/Runs/PlaybookRunsStatus.tsx | 48 ++++++++++++++++++- src/pages/playbooks/PlaybookRunsDetails.tsx | 2 +- .../AlertDialog/ConfirmationPromptDialog.tsx | 4 +- src/ui/Icons/PlaybookStatusIcon.tsx | 19 ++++---- 10 files changed, 149 insertions(+), 17 deletions(-) create mode 100644 src/components/Playbooks/Runs/ApprovePlaybookRunModal.tsx diff --git a/src/api/services/playbooks.ts b/src/api/services/playbooks.ts index d52ae0f79..c4ff791e2 100644 --- a/src/api/services/playbooks.ts +++ b/src/api/services/playbooks.ts @@ -219,3 +219,10 @@ export async function getPlaybookRuns({ ); return res; } + +export async function approvePlaybookRun(id: string) { + const res = await PlaybookAPI.post<{ + message: string; + }>(`/run/approve/${id}`); + return res.data; +} diff --git a/src/api/types/playbooks.ts b/src/api/types/playbooks.ts index 157b79884..98c3aa763 100644 --- a/src/api/types/playbooks.ts +++ b/src/api/types/playbooks.ts @@ -12,7 +12,8 @@ export type PlaybookRunStatus = | "completed" | "failed" | "pending" - | "waiting"; + | "waiting" + | "pending_approval"; export type PlaybookRunActionStatus = | "completed" diff --git a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx index 75fa25ff5..0aadcf24c 100644 --- a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx +++ b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx @@ -20,9 +20,13 @@ import ShowPlaybookRunsParams from "./ShowParamaters/ShowPlaybookRunsParams"; type PlaybookRunActionsProps = { data: PlaybookRunWithActions; + refetch?: () => void; }; -export default function PlaybookRunsActions({ data }: PlaybookRunActionsProps) { +export default function PlaybookRunsActions({ + data, + refetch = () => {} +}: PlaybookRunActionsProps) { const [selectedAction, setSelectedAction] = useState< PlaybookRunAction | undefined >(() => { @@ -69,7 +73,15 @@ export default function PlaybookRunsActions({ data }: PlaybookRunActionsProps) { } + value={ + + } /> void; + open: boolean; + playbookTitle: string; + playbookRunId: string; + refetch?: () => void; +}; + +export default function ApprovePlaybookRunModal({ + onClose, + open, + playbookTitle, + playbookRunId, + refetch = () => {} +}: ApprovePlaybookRunModalProps) { + const { mutate: approve, isLoading } = useMutation({ + mutationFn: (id: string) => { + return approvePlaybookRun(id); + }, + onError: (error: AxiosError) => { + console.error("Failed to approve playbook run", error); + toastError(`Failed to approve playbook run: ${error.message}`); + }, + onSuccess: () => { + onClose(); + refetch(); + } + }); + + return ( + Are you sure you want to approve this playbook run?

} + onConfirm={() => approve(playbookRunId)} + open={open} + onClose={onClose} + isOpen={open} + yesLabel={isLoading ? "Approving..." : "Approve"} + closeLabel="Cancel" + /> + ); +} diff --git a/src/components/Playbooks/Runs/Filter/PlaybookStatusDropdown.tsx b/src/components/Playbooks/Runs/Filter/PlaybookStatusDropdown.tsx index fe2936161..bd4e1ffeb 100644 --- a/src/components/Playbooks/Runs/Filter/PlaybookStatusDropdown.tsx +++ b/src/components/Playbooks/Runs/Filter/PlaybookStatusDropdown.tsx @@ -32,6 +32,11 @@ const options: StateOption[] = [ icon: statusIconMap["pending"], label: "Pending", value: "pending" + }, + { + icon: statusIconMap["pending_approval"], + label: "Pending Approval", + value: "pending_approval" } ].sort((a, b) => a.label.localeCompare(b.label)); diff --git a/src/components/Playbooks/Runs/PlaybookRunsList.tsx b/src/components/Playbooks/Runs/PlaybookRunsList.tsx index 2fdcf2e58..4acfe8129 100644 --- a/src/components/Playbooks/Runs/PlaybookRunsList.tsx +++ b/src/components/Playbooks/Runs/PlaybookRunsList.tsx @@ -65,9 +65,19 @@ const playbookRunsTableColumns: MRT_ColumnDef[] = [ { header: "Status", accessorKey: "status", - Cell: ({ cell }) => { + Cell: ({ cell, row }) => { const status = cell.getValue(); - return ; + const playbookRunId = row.original.id; + const title = + row.original.playbooks?.title ?? row.original.playbooks?.name!; + + return ( + + ); } }, { diff --git a/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx b/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx index 113c9ad37..bcee0c9af 100644 --- a/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx +++ b/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx @@ -1,9 +1,55 @@ +import { Button } from "@flanksource-ui/ui/Buttons/Button"; +import { useState } from "react"; +import { Tooltip } from "react-tooltip"; import { PlaybookRunsStatusProps, PlaybookStatusIcon } from "../../../ui/Icons/PlaybookStatusIcon"; +import ApprovePlaybookRunModal from "./ApprovePlaybookRunModal"; + +export function PlaybookStatusDescription({ + status, + playbookTitle, + showApproveButton = false, + playbookRunId, + refetch = () => {} +}: PlaybookRunsStatusProps & { + playbookTitle: string; + showApproveButton?: boolean; + playbookRunId: string; + refetch?: () => void; +}) { + const [showApprovalModal, setShowApprovalModal] = useState(false); + + if (status === "pending_approval" && showApproveButton) { + return ( + <> + + { + setShowApprovalModal(false); + }} + open={showApprovalModal} + playbookTitle={playbookTitle} + playbookRunId={playbookRunId} + refetch={refetch} + /> + + + ); + } -export function PlaybookStatusDescription({ status }: PlaybookRunsStatusProps) { return (
diff --git a/src/pages/playbooks/PlaybookRunsDetails.tsx b/src/pages/playbooks/PlaybookRunsDetails.tsx index 576561a46..7561718a1 100644 --- a/src/pages/playbooks/PlaybookRunsDetails.tsx +++ b/src/pages/playbooks/PlaybookRunsDetails.tsx @@ -115,7 +115,7 @@ export default function PlaybookRunsDetailsPage() {
{playbookRun ? ( - + ) : ( )} diff --git a/src/ui/AlertDialog/ConfirmationPromptDialog.tsx b/src/ui/AlertDialog/ConfirmationPromptDialog.tsx index 388c5ca86..00efdb2c0 100644 --- a/src/ui/AlertDialog/ConfirmationPromptDialog.tsx +++ b/src/ui/AlertDialog/ConfirmationPromptDialog.tsx @@ -1,13 +1,13 @@ import { Dialog } from "@headlessui/react"; import { XIcon } from "@heroicons/react/outline"; import clsx from "clsx"; -import { ComponentProps } from "react"; +import React, { ComponentProps } from "react"; import { FaCircleNotch } from "react-icons/fa"; type ConfirmationPromptDialogProps = { isOpen: boolean; title: string; - description: string; + description: React.ReactNode; onClose: () => void; onConfirm: () => void; yesLabel?: string; diff --git a/src/ui/Icons/PlaybookStatusIcon.tsx b/src/ui/Icons/PlaybookStatusIcon.tsx index 9d64025c2..35f4b4a5a 100644 --- a/src/ui/Icons/PlaybookStatusIcon.tsx +++ b/src/ui/Icons/PlaybookStatusIcon.tsx @@ -17,31 +17,34 @@ export const statusIconMap: Record< React.ReactElement > = { completed: ( - + ), cancelled: ( - + ), failed: ( - + ), pending: ( + + ), + pending_approval: ( ), waiting: ( - + ), running: ( - + ), scheduled: ( - + ), sleeping: ( - + ), skipped: ( - + ) }; From 0c5bd5bd699f2e22258208ea571cd504eaf711a0 Mon Sep 17 00:00:00 2001 From: Maina Wycliffe Date: Fri, 25 Oct 2024 17:08:25 +0300 Subject: [PATCH 2/3] Playbook approval from the UI Fixes #2304 --- next.config.js | 2 +- package-lock.json | 14 +- package.json | 5 +- public/mockServiceWorker.js | 222 +++++++++--------- .../Runs/Actions/PlaybookRunsActions.tsx | 36 +-- .../Playbooks/Runs/ApprovePlaybookButton.tsx | 52 ++++ .../Playbooks/Runs/PlaybookRunsList.tsx | 11 +- .../Playbooks/Runs/PlaybookRunsStatus.tsx | 50 +--- src/ui/Icons/PlaybookStatusIcon.tsx | 11 +- 9 files changed, 207 insertions(+), 196 deletions(-) create mode 100644 src/components/Playbooks/Runs/ApprovePlaybookButton.tsx diff --git a/next.config.js b/next.config.js index 38fd18091..028c0dc17 100644 --- a/next.config.js +++ b/next.config.js @@ -92,7 +92,7 @@ const config = { proxyTimeout: 1000 * 60 * 10, esmExternals: "loose" }, - transpilePackages: ["monaco-editor", "@flanksource/icons"] + transpilePackages: ["monaco-editor"] }; module.exports = withBundleAnalyzer(config); diff --git a/package-lock.json b/package-lock.json index b6e8b2fc3..b9ad1775e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@flanksource/flanksource-ui", - "version": "1.0.800", + "version": "1.0.810", "dependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@clerk/nextjs": "^5.3.0", @@ -77,7 +77,8 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.15.0", "react-hot-toast": "^2.2.0", - "react-icons": "^4.11.0", + "react-icons": "^4.12.0", + "react-icons-v5": "npm:react-icons@^5.0.0", "react-image": "^4.1.0", "react-loading-icons": "^1.0.8", "react-mentions": "^4.4.0", @@ -35499,6 +35500,15 @@ "react": "*" } }, + "node_modules/react-icons-v5": { + "name": "react-icons", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", + "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-image": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/react-image/-/react-image-4.1.0.tgz", diff --git a/package.json b/package.json index 2a84b144f..04f7cc62c 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,8 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.15.0", "react-hot-toast": "^2.2.0", - "react-icons": "^4.11.0", + "react-icons": "^4.12.0", + "react-icons-v5": "npm:react-icons@^5.0.0", "react-image": "^4.1.0", "react-loading-icons": "^1.0.8", "react-mentions": "^4.4.0", @@ -221,4 +222,4 @@ "msw": { "workerDirectory": "public" } -} +} \ No newline at end of file diff --git a/public/mockServiceWorker.js b/public/mockServiceWorker.js index f7c6e5f52..51d85eeeb 100644 --- a/public/mockServiceWorker.js +++ b/public/mockServiceWorker.js @@ -8,121 +8,121 @@ * - Please do NOT serve this file on production. */ -const INTEGRITY_CHECKSUM = "3d6b9f06410d179a7f7404d4bf4c3c70"; -const activeClientIds = new Set(); +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' +const activeClientIds = new Set() -self.addEventListener("install", function () { - self.skipWaiting(); -}); +self.addEventListener('install', function () { + self.skipWaiting() +}) -self.addEventListener("activate", function (event) { - event.waitUntil(self.clients.claim()); -}); +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) -self.addEventListener("message", async function (event) { - const clientId = event.source.id; +self.addEventListener('message', async function (event) { + const clientId = event.source.id if (!clientId || !self.clients) { - return; + return } - const client = await self.clients.get(clientId); + const client = await self.clients.get(clientId) if (!client) { - return; + return } const allClients = await self.clients.matchAll({ - type: "window" - }); + type: 'window', + }) switch (event.data) { - case "KEEPALIVE_REQUEST": { + case 'KEEPALIVE_REQUEST': { sendToClient(client, { - type: "KEEPALIVE_RESPONSE" - }); - break; + type: 'KEEPALIVE_RESPONSE', + }) + break } - case "INTEGRITY_CHECK_REQUEST": { + case 'INTEGRITY_CHECK_REQUEST': { sendToClient(client, { - type: "INTEGRITY_CHECK_RESPONSE", - payload: INTEGRITY_CHECKSUM - }); - break; + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }) + break } - case "MOCK_ACTIVATE": { - activeClientIds.add(clientId); + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) sendToClient(client, { - type: "MOCKING_ENABLED", - payload: true - }); - break; + type: 'MOCKING_ENABLED', + payload: true, + }) + break } - case "MOCK_DEACTIVATE": { - activeClientIds.delete(clientId); - break; + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break } - case "CLIENT_CLOSED": { - activeClientIds.delete(clientId); + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) const remainingClients = allClients.filter((client) => { - return client.id !== clientId; - }); + return client.id !== clientId + }) // Unregister itself when there are no more clients if (remainingClients.length === 0) { - self.registration.unregister(); + self.registration.unregister() } - break; + break } } -}); +}) -self.addEventListener("fetch", function (event) { - const { request } = event; - const accept = request.headers.get("accept") || ""; +self.addEventListener('fetch', function (event) { + const { request } = event + const accept = request.headers.get('accept') || '' // Bypass server-sent events. - if (accept.includes("text/event-stream")) { - return; + if (accept.includes('text/event-stream')) { + return } // Bypass navigation requests. - if (request.mode === "navigate") { - return; + if (request.mode === 'navigate') { + return } // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. - if (request.cache === "only-if-cached" && request.mode !== "same-origin") { - return; + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return } // Bypass all requests when there are no active clients. // Prevents the self-unregistered worked from handling requests // after it's been deleted (still remains active until the next reload). if (activeClientIds.size === 0) { - return; + return } // Generate unique request ID. - const requestId = Math.random().toString(16).slice(2); + const requestId = Math.random().toString(16).slice(2) event.respondWith( handleRequest(event, requestId).catch((error) => { - if (error.name === "NetworkError") { + if (error.name === 'NetworkError') { console.warn( '[MSW] Successfully emulated a network error for the "%s %s" request.', request.method, - request.url - ); - return; + request.url, + ) + return } // At this point, any exception indicates an issue with the original request/response. @@ -131,24 +131,24 @@ self.addEventListener("fetch", function (event) { [MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, request.method, request.url, - `${error.name}: ${error.message}` - ); - }) - ); -}); + `${error.name}: ${error.message}`, + ) + }), + ) +}) async function handleRequest(event, requestId) { - const client = await resolveMainClient(event); - const response = await getResponse(event, client, requestId); + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) // Send back the response clone for the "response:*" life-cycle events. // Ensure MSW is active and ready to handle the message, otherwise // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { - (async function () { - const clonedResponse = response.clone(); + ;(async function () { + const clonedResponse = response.clone() sendToClient(client, { - type: "RESPONSE", + type: 'RESPONSE', payload: { requestId, type: clonedResponse.type, @@ -158,13 +158,13 @@ async function handleRequest(event, requestId) { body: clonedResponse.body === null ? null : await clonedResponse.text(), headers: Object.fromEntries(clonedResponse.headers.entries()), - redirected: clonedResponse.redirected - } - }); - })(); + redirected: clonedResponse.redirected, + }, + }) + })() } - return response; + return response } // Resolve the main client for the given event. @@ -172,49 +172,49 @@ async function handleRequest(event, requestId) { // that registered the worker. It's with the latter the worker should // communicate with during the response resolving phase. async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId); + const client = await self.clients.get(event.clientId) - if (client?.frameType === "top-level") { - return client; + if (client?.frameType === 'top-level') { + return client } const allClients = await self.clients.matchAll({ - type: "window" - }); + type: 'window', + }) return allClients .filter((client) => { // Get only those clients that are currently visible. - return client.visibilityState === "visible"; + return client.visibilityState === 'visible' }) .find((client) => { // Find the client ID that's recorded in the // set of clients that have registered the worker. - return activeClientIds.has(client.id); - }); + return activeClientIds.has(client.id) + }) } async function getResponse(event, client, requestId) { - const { request } = event; - const clonedRequest = request.clone(); + const { request } = event + const clonedRequest = request.clone() function passthrough() { // Clone the request because it might've been already used // (i.e. its body has been read and sent to the client). - const headers = Object.fromEntries(clonedRequest.headers.entries()); + const headers = Object.fromEntries(clonedRequest.headers.entries()) // Remove MSW-specific request headers so the bypassed requests // comply with the server's CORS preflight check. // Operate with the headers as an object because request "Headers" // are immutable. - delete headers["x-msw-bypass"]; + delete headers['x-msw-bypass'] - return fetch(clonedRequest, { headers }); + return fetch(clonedRequest, { headers }) } // Bypass mocking when the client is not active. if (!client) { - return passthrough(); + return passthrough() } // Bypass initial page load requests (i.e. static assets). @@ -222,18 +222,18 @@ async function getResponse(event, client, requestId) { // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet // and is not ready to handle requests. if (!activeClientIds.has(client.id)) { - return passthrough(); + return passthrough() } // Bypass requests with the explicit bypass header. // Such requests can be issued by "ctx.fetch()". - if (request.headers.get("x-msw-bypass") === "true") { - return passthrough(); + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough() } // Notify the client that a request has been intercepted. const clientMessage = await sendToClient(client, { - type: "REQUEST", + type: 'REQUEST', payload: { id: requestId, url: request.url, @@ -249,55 +249,55 @@ async function getResponse(event, client, requestId) { referrerPolicy: request.referrerPolicy, body: await request.text(), bodyUsed: request.bodyUsed, - keepalive: request.keepalive - } - }); + keepalive: request.keepalive, + }, + }) switch (clientMessage.type) { - case "MOCK_RESPONSE": { - return respondWithMock(clientMessage.data); + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) } - case "MOCK_NOT_FOUND": { - return passthrough(); + case 'MOCK_NOT_FOUND': { + return passthrough() } - case "NETWORK_ERROR": { - const { name, message } = clientMessage.data; - const networkError = new Error(message); - networkError.name = name; + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.data + const networkError = new Error(message) + networkError.name = name // Rejecting a "respondWith" promise emulates a network error. - throw networkError; + throw networkError } } - return passthrough(); + return passthrough() } function sendToClient(client, message) { return new Promise((resolve, reject) => { - const channel = new MessageChannel(); + const channel = new MessageChannel() channel.port1.onmessage = (event) => { if (event.data && event.data.error) { - return reject(event.data.error); + return reject(event.data.error) } - resolve(event.data); - }; + resolve(event.data) + } - client.postMessage(message, [channel.port2]); - }); + client.postMessage(message, [channel.port2]) + }) } function sleep(timeMs) { return new Promise((resolve) => { - setTimeout(resolve, timeMs); - }); + setTimeout(resolve, timeMs) + }) } async function respondWithMock(response) { - await sleep(response.delay); - return new Response(response.body, response); + await sleep(response.delay) + return new Response(response.body, response) } diff --git a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx index 0aadcf24c..01f4e7c40 100644 --- a/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx +++ b/src/components/Playbooks/Runs/Actions/PlaybookRunsActions.tsx @@ -10,6 +10,7 @@ import dayjs from "dayjs"; import { useMemo, useState } from "react"; import { Link } from "react-router-dom"; import PlaybookSpecIcon from "../../Settings/PlaybookSpecIcon"; +import { ApprovePlaybookButton } from "../ApprovePlaybookButton"; import { getResourceForRun } from "../services"; import ReRunPlaybookWithParamsButton from "../Submit/ReRunPlaybookWithParamsButton"; import { PlaybookStatusDescription } from "./../PlaybookRunsStatus"; @@ -73,15 +74,7 @@ export default function PlaybookRunsActions({ - } + value={} /> )}
-
- +
+
+ + + +
diff --git a/src/components/Playbooks/Runs/ApprovePlaybookButton.tsx b/src/components/Playbooks/Runs/ApprovePlaybookButton.tsx new file mode 100644 index 000000000..56bef9bc5 --- /dev/null +++ b/src/components/Playbooks/Runs/ApprovePlaybookButton.tsx @@ -0,0 +1,52 @@ +import { PlaybookRunStatus } from "@flanksource-ui/api/types/playbooks"; +import { Button } from "@flanksource-ui/ui/Buttons/Button"; +import { useState } from "react"; +import { BsCheck2 } from "react-icons/bs"; +import { Tooltip } from "react-tooltip"; +import ApprovePlaybookRunModal from "./ApprovePlaybookRunModal"; + +type ApprovePlaybookButtonProps = { + playbookTitle: string; + playbookRunId: string; + status: PlaybookRunStatus; + refetch: () => void; +}; + +export function ApprovePlaybookButton({ + playbookTitle, + playbookRunId, + status, + refetch = () => {} +}: ApprovePlaybookButtonProps) { + const [showApprovalModal, setShowApprovalModal] = useState(false); + + if (status !== "pending_approval") { + return null; + } + + return ( + <> + + { + setShowApprovalModal(false); + }} + open={showApprovalModal} + playbookTitle={playbookTitle} + playbookRunId={playbookRunId} + refetch={refetch} + /> + + + ); +} diff --git a/src/components/Playbooks/Runs/PlaybookRunsList.tsx b/src/components/Playbooks/Runs/PlaybookRunsList.tsx index 4acfe8129..cf11153d4 100644 --- a/src/components/Playbooks/Runs/PlaybookRunsList.tsx +++ b/src/components/Playbooks/Runs/PlaybookRunsList.tsx @@ -67,17 +67,8 @@ const playbookRunsTableColumns: MRT_ColumnDef[] = [ accessorKey: "status", Cell: ({ cell, row }) => { const status = cell.getValue(); - const playbookRunId = row.original.id; - const title = - row.original.playbooks?.title ?? row.original.playbooks?.name!; - return ( - - ); + return ; } }, { diff --git a/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx b/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx index bcee0c9af..ee92018bd 100644 --- a/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx +++ b/src/components/Playbooks/Runs/PlaybookRunsStatus.tsx @@ -1,55 +1,9 @@ -import { Button } from "@flanksource-ui/ui/Buttons/Button"; -import { useState } from "react"; -import { Tooltip } from "react-tooltip"; import { PlaybookRunsStatusProps, PlaybookStatusIcon -} from "../../../ui/Icons/PlaybookStatusIcon"; -import ApprovePlaybookRunModal from "./ApprovePlaybookRunModal"; - -export function PlaybookStatusDescription({ - status, - playbookTitle, - showApproveButton = false, - playbookRunId, - refetch = () => {} -}: PlaybookRunsStatusProps & { - playbookTitle: string; - showApproveButton?: boolean; - playbookRunId: string; - refetch?: () => void; -}) { - const [showApprovalModal, setShowApprovalModal] = useState(false); - - if (status === "pending_approval" && showApproveButton) { - return ( - <> - - { - setShowApprovalModal(false); - }} - open={showApprovalModal} - playbookTitle={playbookTitle} - playbookRunId={playbookRunId} - refetch={refetch} - /> - - - ); - } +} from "@flanksource-ui/ui/Icons/PlaybookStatusIcon"; +export function PlaybookStatusDescription({ status }: PlaybookRunsStatusProps) { return (
diff --git a/src/ui/Icons/PlaybookStatusIcon.tsx b/src/ui/Icons/PlaybookStatusIcon.tsx index 35f4b4a5a..5bfbd4584 100644 --- a/src/ui/Icons/PlaybookStatusIcon.tsx +++ b/src/ui/Icons/PlaybookStatusIcon.tsx @@ -1,3 +1,8 @@ +import { + PlaybookRunActionStatus, + PlaybookRunStatus +} from "@flanksource-ui/api/types/playbooks"; +import { TbCircleDashedCheck } from "react-icons-v5/tb"; import { BsCheckCircle, BsCircle, @@ -7,10 +12,6 @@ import { BsStopCircle, BsXCircle } from "react-icons/bs"; -import { - PlaybookRunActionStatus, - PlaybookRunStatus -} from "../../api/types/playbooks"; export const statusIconMap: Record< PlaybookRunStatus | PlaybookRunActionStatus, @@ -29,7 +30,7 @@ export const statusIconMap: Record< ), pending_approval: ( - + ), waiting: ( From 9b48f56b36e52c3bddfa668c4b221f7631dbadfe Mon Sep 17 00:00:00 2001 From: Moshe Immerman Date: Tue, 29 Oct 2024 09:55:43 +0200 Subject: [PATCH 3/3] chore: update button title --- src/components/Playbooks/Runs/ApprovePlaybookButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Playbooks/Runs/ApprovePlaybookButton.tsx b/src/components/Playbooks/Runs/ApprovePlaybookButton.tsx index 56bef9bc5..9be491d41 100644 --- a/src/components/Playbooks/Runs/ApprovePlaybookButton.tsx +++ b/src/components/Playbooks/Runs/ApprovePlaybookButton.tsx @@ -35,7 +35,7 @@ export function ApprovePlaybookButton({ }} > - Pending Approval + Approve {