Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: incident timeline #1895

Merged
merged 16 commits into from
Sep 19, 2024
Merged
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
Loading