Skip to content

Commit

Permalink
feat: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
shahargl committed Sep 30, 2024
1 parent 4e90673 commit e1700c9
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 87 deletions.
176 changes: 176 additions & 0 deletions keep-ui/app/incidents/[id]/incident-workflow-sidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { Fragment, useEffect } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { Text, Button, TextInput, Badge, Title, Card } from "@tremor/react";
import { IoMdClose } from "react-icons/io";
import { WorkflowExecution } from "app/workflows/builder/types";
import { formatDistanceToNowStrict } from "date-fns";
import {
getIcon,
getTriggerIcon,
extractTriggerValue,
} from "app/workflows/[workflow_id]/workflow-execution-table";
import { useWorkflowExecution } from "utils/hooks/useWorkflowExecutions";

interface IncidentWorkflowSidebarProps {
isOpen: boolean;
toggle: VoidFunction;
selectedExecution: WorkflowExecution | null;
}

const IncidentWorkflowSidebar: React.FC<IncidentWorkflowSidebarProps> = ({
isOpen,
toggle,
selectedExecution,
}) => {
const { data: workflowExecutionData } = useWorkflowExecution(
selectedExecution?.workflow_id || "",
selectedExecution?.id || ""
);

useEffect(() => {
if (isOpen && selectedExecution) {
// You can add any side effects here when the sidebar opens
}
}, [isOpen, selectedExecution]);

if (!selectedExecution) return null;

const customFormatter = (value: number, unit: string, suffix: string) => {
if (!selectedExecution?.started) {
return "";
}

const formattedString = formatDistanceToNowStrict(
new Date(selectedExecution.started + "Z"),
{ addSuffix: true }
);

return formattedString
.replace("about ", "")
.replace("minute", "min")
.replace("second", "sec")
.replace("hour", "hr");
};

return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog onClose={toggle}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/30 z-20" aria-hidden="true" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition ease-in-out duration-300 transform"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="fixed right-0 inset-y-0 w-2/4 bg-white z-30 p-6 overflow-auto flex flex-col">
<div className="flex justify-between mb-4">
<div>
<Dialog.Title className="text-3xl font-bold" as={Title}>
Workflow Execution Details
<Badge className="ml-4" color="orange">
{selectedExecution.status}
</Badge>
</Dialog.Title>
</div>
<div>
<Button onClick={toggle} variant="light">
<IoMdClose className="h-6 w-6 text-gray-500" />
</Button>
</div>
</div>

<div className="flex-grow space-y-4">
<Card>
<div className="space-y-4">
<div>
<Text className="block text-sm font-medium text-gray-700 mb-2">
Execution ID
</Text>
<TextInput value={selectedExecution.id} readOnly />
</div>
<div>
<Text className="block text-sm font-medium text-gray-700 mb-2">
Status
</Text>
<div className="flex items-center">
{getIcon(selectedExecution.status)}
<span className="ml-2">{selectedExecution.status}</span>
</div>
</div>
<div>
<Text className="block text-sm font-medium text-gray-700 mb-2">
Triggered By
</Text>
<Button
className="px-3 py-0.5 bg-white text-black rounded-xl border-2 border-gray-400 inline-flex items-center gap-2 font-bold hover:bg-white border-gray-400"
variant="secondary"
tooltip={selectedExecution.triggered_by ?? ""}
icon={getTriggerIcon(
extractTriggerValue(selectedExecution.triggered_by)
)}
>
<div>
{extractTriggerValue(selectedExecution.triggered_by)}
</div>
</Button>
</div>
<div>
<Text className="block text-sm font-medium text-gray-700 mb-2">
Execution Time
</Text>
<TextInput
value={
selectedExecution.execution_time
? `${selectedExecution.execution_time} seconds`
: "N/A"
}
readOnly
/>
</div>
<div>
<Text className="block text-sm font-medium text-gray-700 mb-2">
Started At
</Text>
<TextInput value={selectedExecution.started} readOnly />
</div>
</div>
</Card>

<Card>
<Text className="block text-sm font-medium text-gray-700 mb-2">
Execution Logs
</Text>
<div className="bg-gray-100 p-4 rounded-md overflow-auto max-h-96">
<pre className="whitespace-pre-wrap">
{Array.isArray(workflowExecutionData?.logs)
? workflowExecutionData.logs.map((log, index) => (
<div key={index}>
{log.timestamp} - {log.message}
</div>
))
: workflowExecutionData?.logs || "No logs available"}
</pre>
</div>
</Card>
</div>
</Dialog.Panel>
</Transition.Child>
</Dialog>
</Transition>
);
};

export default IncidentWorkflowSidebar;
103 changes: 31 additions & 72 deletions keep-ui/app/incidents/[id]/incident-workflow-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ import {
Button,
Badge,
} from "@tremor/react";
import {
ExclamationTriangleIcon,
ChevronDownIcon,
ChevronUpIcon,
} from "@radix-ui/react-icons";
import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";
import { IncidentDto } from "../models";
Expand All @@ -37,6 +33,7 @@ import {
extractTriggerValue,
extractTriggerDetails,
} from "app/workflows/[workflow_id]/workflow-execution-table";
import IncidentWorkflowSidebar from "./incident-workflow-sidebar";

interface Props {
incident: IncidentDto;
Expand All @@ -55,7 +52,9 @@ export default function IncidentWorkflowTable({ incident }: Props) {
limit: 20,
offset: 0,
});
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [selectedExecution, setSelectedExecution] =
useState<WorkflowExecution | null>(null);

const { data: workflows, isLoading } = useIncidentWorkflowExecutions(
incident.id,
Expand Down Expand Up @@ -84,6 +83,15 @@ export default function IncidentWorkflowTable({ incident }: Props) {
}
}, [pagination, workflows]);

const toggleSidebar = () => {
setIsSidebarOpen(!isSidebarOpen);
};

const handleRowClick = (execution: WorkflowExecution) => {
setSelectedExecution(execution);
toggleSidebar();
};

const columns = [
columnHelper.accessor("workflow_name", {
header: "Name",
Expand Down Expand Up @@ -162,21 +170,6 @@ export default function IncidentWorkflowTable({ incident }: Props) {
) : null;
},
}),
columnHelper.display({
id: "expand",
header: "",
cell: ({ row }) => (
<Button
variant="light"
onClick={(e) => {
e.stopPropagation();
toggleRowExpansion(row.id);
}}
>
{expandedRows.has(row.id) ? <ChevronUpIcon /> : <ChevronDownIcon />}
</Button>
),
}),
];

const table = useReactTable({
Expand All @@ -191,18 +184,6 @@ export default function IncidentWorkflowTable({ incident }: Props) {
onPaginationChange: setTablePagination,
});

const toggleRowExpansion = (rowId: string) => {
setExpandedRows((prev) => {
const newSet = new Set(prev);
if (newSet.has(rowId)) {
newSet.delete(rowId);
} else {
newSet.add(rowId);
}
return newSet;
});
};

return (
<>
{!isLoading && (workflows?.items ?? []).length === 0 && (
Expand Down Expand Up @@ -234,45 +215,17 @@ export default function IncidentWorkflowTable({ incident }: Props) {
{workflows && workflows.items.length > 0 && (
<TableBody>
{table.getRowModel().rows.map((row) => (
<React.Fragment key={row.id}>
<TableRow
className="hover:bg-slate-100 cursor-pointer"
onClick={() => toggleRowExpansion(row.id)}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</TableCell>
))}
</TableRow>
{expandedRows.has(row.id) && (
<TableRow>
<TableCell colSpan={columns.length}>
<div className="p-4 bg-gray-50 flex">
<div className="w-1/2 pr-2">
<h3 className="font-bold mb-2">Logs:</h3>
<pre className="whitespace-pre-wrap">
{Array.isArray(row.original.logs)
? row.original.logs
.map((log) => JSON.stringify(log))
.join("\n")
: String(row.original.logs)}
</pre>
</div>
<div className="w-1/2 pl-2">
<h3 className="font-bold mb-2">Results:</h3>
<pre className="whitespace-pre-wrap">
{JSON.stringify(row.original.results, null, 2)}
</pre>
</div>
</div>
</TableCell>
</TableRow>
)}
</React.Fragment>
<TableRow
key={row.id}
className="hover:bg-slate-100 cursor-pointer"
onClick={() => handleRowClick(row.original)}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
)}
Expand All @@ -296,6 +249,12 @@ export default function IncidentWorkflowTable({ incident }: Props) {
<div className="mt-4 mb-8">
<IncidentPagination table={table} isRefreshAllowed={true} />
</div>

<IncidentWorkflowSidebar
isOpen={isSidebarOpen}
toggle={toggleSidebar}
selectedExecution={selectedExecution}
/>
</>
);
}
2 changes: 1 addition & 1 deletion keep-ui/app/incidents/[id]/incident.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function IncidentView({ incidentId }: Props) {
<TabList
variant="line"
color="orange"
className="sticky xl:-top-10 -top-4 bg-white z-[100]"
className="sticky xl:-top-10 -top-4 bg-white"
>
<Tab icon={CiBellOn}>Alerts</Tab>
<Tab icon={CiViewTimeline}>Timeline</Tab>
Expand Down
21 changes: 14 additions & 7 deletions keep-ui/app/workflows/[workflow_id]/workflow-execution-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,24 @@ export function extractTriggerDetails(
details = triggered_by.substring("type:alert".length).trim();
} else if (triggered_by.startsWith("manual")) {
details = triggered_by.substring("manual".length).trim();
} else if (triggered_by.startsWith("type:incident")) {
const [operator, ...rest] = triggered_by
.substring("type:incident:".length)
.trim()
.split(" ");
details = `${rest.join(" ")}`;
} else if (triggered_by.startsWith("type:incident:")) {
// Handle 'type:incident:{some operator}' by removing the operator
details = triggered_by.substring("type:incident:".length).trim();
const firstSpaceIndex = details.indexOf(" ");
if (firstSpaceIndex > -1) {
details = details.substring(firstSpaceIndex).trim();
} else {
details = "";
}
} else {
details = triggered_by;
}

return details.split(" ");
// Split the string into key-value pairs, where values may contain spaces
const regex = /\b(\w+:[^:]+?)(?=\s\w+:|$)/g;
const matches = details.match(regex);

return matches ? matches : [];
}

export function ExecutionTable({ executions, setPagination }: Props) {
Expand Down
Loading

0 comments on commit e1700c9

Please sign in to comment.