Skip to content

Commit

Permalink
feat: incident timeline (#1895)
Browse files Browse the repository at this point in the history
Signed-off-by: Tal <[email protected]>
  • Loading branch information
talboren authored Sep 19, 2024
1 parent 135e967 commit c1a3c5e
Show file tree
Hide file tree
Showing 17 changed files with 731 additions and 120 deletions.
5 changes: 3 additions & 2 deletions keep-ui/app/alerts/alert-severity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import {

interface Props {
severity: Severity | undefined;
marginLeft?: boolean;
}

export default function AlertSeverity({ severity }: Props) {
export default function AlertSeverity({ severity, marginLeft = true }: Props) {
let icon: any;
let color: any;
let severityText: string;
Expand Down Expand Up @@ -56,7 +57,7 @@ export default function AlertSeverity({ severity }: Props) {
icon={icon}
tooltip={severityText}
size="sm"
className="ml-2.5"
className={marginLeft ? "ml-2.5" : ""}
/>
);
}
2 changes: 1 addition & 1 deletion keep-ui/app/alerts/alert-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const AlertSidebar = ({ isOpen, toggle, alert }: AlertSidebarProps) => {
<AlertTimeline
key={auditData ? auditData.length : 1}
alert={alert}
auditData={auditData}
auditData={auditData || []}
isLoading={isLoading}
onRefresh={handleRefresh}
/>
Expand Down
3 changes: 1 addition & 2 deletions keep-ui/app/alerts/alert-table-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { useState } from "react";
import {
ColumnDef,
FilterFn,
Row,
RowSelectionState,
VisibilityState,
createColumnHelper,
Expand All @@ -18,7 +17,7 @@ import AlertAssignee from "./alert-assignee";
import AlertExtraPayload from "./alert-extra-payload";
import AlertMenu from "./alert-menu";
import { isSameDay, isValid, isWithinInterval, startOfDay } from "date-fns";
import { Severity, severityMapping } from "./models";
import { severityMapping } from "./models";
import { MdOutlineNotificationsActive, MdOutlineNotificationsOff } from "react-icons/md";

export const DEFAULT_COLS = [
Expand Down
25 changes: 11 additions & 14 deletions keep-ui/app/alerts/alert-timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React, { useState } from "react";
import React from "react";
import { Subtitle, Button } from "@tremor/react";
import { Chrono } from "react-chrono";
import Image from "next/image";
import { ArrowPathIcon } from "@heroicons/react/24/outline";
import { AlertDto } from "./models";
import { AuditEvent } from "utils/hooks/useAlerts";

const getInitials = (name: string) =>
((name.match(/(^\S\S?|\b\S)?/g) ?? []).join("").match(/(^\S|\S$)?/g) ?? [])
Expand All @@ -15,21 +16,19 @@ const formatTimestamp = (timestamp: Date | string) => {
return date.toLocaleString();
};

type AuditEvent = {
user_id: string;
action: string;
description: string;
timestamp: string;
};

type AlertTimelineProps = {
alert: AlertDto | null;
auditData: AuditEvent[];
isLoading: boolean;
onRefresh: () => void;
};

const AlertTimeline: React.FC<AlertTimelineProps> = ({ alert, auditData, isLoading, onRefresh }) => {
const AlertTimeline: React.FC<AlertTimelineProps> = ({
alert,
auditData,
isLoading,
onRefresh,
}) => {
// Default audit event if no audit data is available
const defaultAuditEvent = alert
? [
Expand Down Expand Up @@ -97,11 +96,9 @@ const AlertTimeline: React.FC<AlertTimelineProps> = ({ alert, auditData, isLoadi
<div className="flex-grow">
<Chrono
items={
auditContent.map(
(entry) => ({
title: formatTimestamp(entry.timestamp),
})
) || []
auditContent.map((entry) => ({
title: formatTimestamp(entry.timestamp),
})) || []
}
hideControls
disableToolbar
Expand Down
54 changes: 30 additions & 24 deletions keep-ui/app/incidents/[id]/incident-alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import AlertName from "app/alerts/alert-name";
import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
import IncidentAlertMenu from "./incident-alert-menu";
import IncidentPagination from "../incident-pagination";
import React, {Dispatch, SetStateAction, useEffect, useState} from "react";
import {IncidentDto} from "../models";
import React, { useEffect, useState } from "react";
import { IncidentDto } from "../models";

interface Props {
incident: IncidentDto;
Expand All @@ -39,7 +39,6 @@ interface Pagination {
offset: number;
}


const columnHelper = createColumnHelper<AlertDto>();

export default function IncidentAlerts({ incident }: Props) {
Expand All @@ -48,28 +47,32 @@ export default function IncidentAlerts({ incident }: Props) {
offset: 0,
});

const { data: alerts, isLoading } = useIncidentAlerts(incident.id, alertsPagination.limit, alertsPagination.offset);
const { data: alerts, isLoading } = useIncidentAlerts(
incident.id,
alertsPagination.limit,
alertsPagination.offset
);

const [pagination, setTablePagination] = useState({
pageIndex: alerts? Math.ceil(alerts.offset / alerts.limit) : 0,
pageSize: alerts? alerts.limit : 20,
pageIndex: alerts ? Math.ceil(alerts.offset / alerts.limit) : 0,
pageSize: alerts ? alerts.limit : 20,
});

useEffect(() => {
if (alerts && alerts.limit != pagination.pageSize) {
setAlertsPagination({
limit: pagination.pageSize,
offset: 0,
})
});
}
const currentOffset = pagination.pageSize * pagination.pageIndex;
if (alerts && alerts.offset != currentOffset) {
setAlertsPagination({
limit: pagination.pageSize,
offset: currentOffset,
})
});
}
}, [pagination])
}, [pagination]);
usePollIncidentAlerts(incident.id);

const columns = [
Expand Down Expand Up @@ -116,7 +119,7 @@ export default function IncidentAlerts({ incident }: Props) {
(context.getValue() ?? []).map((source, index) => (
<Image
className={`inline-block ${index == 0 ? "" : "-ml-2"}`}
key={source}
key={`source-${source}-${index}`}
alt={source}
height={24}
width={24}
Expand All @@ -128,13 +131,13 @@ export default function IncidentAlerts({ incident }: Props) {
columnHelper.display({
id: "remove",
header: "",
cell: (context) => (
incident.is_confirmed &&
cell: (context) =>
incident.is_confirmed && (
<IncidentAlertMenu
alert={context.row.original}
incidentId={incident.id}
/>
),
),
}),
];

Expand Down Expand Up @@ -164,9 +167,9 @@ export default function IncidentAlerts({ incident }: Props) {
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
{headerGroup.headers.map((header, index) => {
return (
<TableHeaderCell key={header.id}>
<TableHeaderCell key={`header-${header.id}-${index}`}>
{flexRender(
header.column.columnDef.header,
header.getContext()
Expand All @@ -179,10 +182,13 @@ export default function IncidentAlerts({ incident }: Props) {
</TableHead>
{alerts && alerts?.items?.length > 0 && (
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id} className="hover:bg-slate-100">
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{table.getRowModel().rows.map((row, index) => (
<TableRow
key={`row-${row.id}-${index}`}
className="hover:bg-slate-100"
>
{row.getVisibleCells().map((cell, index) => (
<TableCell key={`cell-${cell.id}-${index}`}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
Expand All @@ -196,10 +202,10 @@ export default function IncidentAlerts({ incident }: Props) {
<TableBody>
{Array(pagination.pageSize)
.fill("")
.map((index) => (
<TableRow key={index}>
{columns.map((c) => (
<TableCell key={c.id}>
.map((index, rowIndex) => (
<TableRow key={`row-${index}-${rowIndex}`}>
{columns.map((c, cellIndex) => (
<TableCell key={`cell-${c.id}-${cellIndex}`}>
<Skeleton />
</TableCell>
))}
Expand All @@ -211,7 +217,7 @@ export default function IncidentAlerts({ incident }: Props) {
</Table>

<div className="mt-4 mb-8">
<IncidentPagination table={table} isRefreshAllowed={true}/>
<IncidentPagination table={table} isRefreshAllowed={true} />
</div>
</>
);
Expand Down
75 changes: 53 additions & 22 deletions keep-ui/app/incidents/[id]/incident-info.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@

import {Button, Title} from "@tremor/react";
import { Button, Title } from "@tremor/react";
import { IncidentDto } from "../models";
import CreateOrUpdateIncident from "../create-or-update-incident";
import Modal from "@/components/ui/Modal";
import React, {useState} from "react";
import {MdBlock, MdDone, MdModeEdit} from "react-icons/md";
import {useIncident} from "../../../utils/hooks/useIncidents";
import {deleteIncident, handleConfirmPredictedIncident} from "../incident-candidate-actions";
import {useSession} from "next-auth/react";
import {useRouter} from "next/navigation";
import React, { useState } from "react";
import { MdBlock, MdDone, MdModeEdit } from "react-icons/md";
import { useIncident } from "../../../utils/hooks/useIncidents";
import {
deleteIncident,
handleConfirmPredictedIncident,
} from "../incident-candidate-actions";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { format } from "date-fns";
// import { RiSparkling2Line } from "react-icons/ri";

interface Props {
Expand All @@ -34,12 +37,16 @@ export default function IncidentInformation({ incident }: Props) {
mutate();
};

const formatString = "dd, MMM yyyy - HH:mm.ss 'UTC'";

return (
<div className="flex h-full flex-col justify-between">
<div>
<div className="flex justify-between mb-2.5">
<Title className="">{incident.is_confirmed ? "⚔️ " : "Possible "}Incident Information</Title>
{incident.is_confirmed &&
<Title className="">
{incident.is_confirmed ? "⚔️ " : "Possible "}Incident Information
</Title>
{incident.is_confirmed && (
<Button
color="orange"
size="xs"
Expand All @@ -51,9 +58,11 @@ export default function IncidentInformation({ incident }: Props) {
handleStartEdit();
}}
/>
}
{!incident.is_confirmed &&
<div className={"space-x-1 flex flex-row items-center justify-center"}>
)}
{!incident.is_confirmed && (
<div
className={"space-x-1 flex flex-row items-center justify-center"}
>
<Button
color="orange"
size="xs"
Expand All @@ -64,9 +73,15 @@ export default function IncidentInformation({ incident }: Props) {
onClick={(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
handleConfirmPredictedIncident({incidentId: incident.id!, mutate, session});
handleConfirmPredictedIncident({
incidentId: incident.id!,
mutate,
session,
});
}}
>Confirm</Button>
>
Confirm
</Button>
<Button
color="red"
size="xs"
Expand All @@ -76,21 +91,37 @@ export default function IncidentInformation({ incident }: Props) {
onClick={async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const success = await deleteIncident({incidentId: incident.id!, mutate, session});
const success = await deleteIncident({
incidentId: incident.id!,
mutate,
session,
});
if (success) {
router.push("/incidents");
}
}}
/>
</div>
}
)}
</div>
<div className="prose-2xl">
{incident.user_generated_name || incident.ai_generated_name}
</div>
<div className="prose-2xl">{incident.user_generated_name || incident.ai_generated_name}</div>
<p>Summary: {incident.user_summary || incident.generated_summary}</p>
{!!incident.start_time && <p>Started at: {new Date(incident.start_time + "Z").toLocaleString()}</p>}
{!!incident.last_seen_time && <p>Last seen at: {new Date(incident.last_seen_time + "Z").toLocaleString()}</p>}
{!!incident.rule_fingerprint && <p>Group by value: {incident.rule_fingerprint}</p>}

{!!incident.start_time && (
<p>
Started at: {format(new Date(incident.start_time), formatString)}
</p>
)}
{!!incident.last_seen_time && (
<p>
Last seen at:{" "}
{format(new Date(incident.last_seen_time), formatString)}
</p>
)}
{!!incident.rule_fingerprint && (
<p>Group by value: {incident.rule_fingerprint}</p>
)}
</div>
<Modal
isOpen={isFormOpen}
Expand Down
Loading

0 comments on commit c1a3c5e

Please sign in to comment.