diff --git a/keep-ui/app/incidents/[id]/incident-alerts.tsx b/keep-ui/app/incidents/[id]/incident-alerts.tsx index d985b2ebac..cf32d87d4f 100644 --- a/keep-ui/app/incidents/[id]/incident-alerts.tsx +++ b/keep-ui/app/incidents/[id]/incident-alerts.tsx @@ -28,9 +28,10 @@ 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 "../model"; interface Props { - incidentId: string; + incident: IncidentDto; } interface Pagination { @@ -41,13 +42,13 @@ interface Pagination { const columnHelper = createColumnHelper(); -export default function IncidentAlerts({ incidentId }: Props) { +export default function IncidentAlerts({ incident }: Props) { const [alertsPagination, setAlertsPagination] = useState({ limit: 20, offset: 0, }); - const { data: alerts, isLoading } = useIncidentAlerts(incidentId, 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, @@ -69,7 +70,7 @@ export default function IncidentAlerts({ incidentId }: Props) { }) } }, [pagination]) - usePollIncidentAlerts(incidentId); + usePollIncidentAlerts(incident.id); const columns = [ columnHelper.accessor("severity", { @@ -128,10 +129,11 @@ export default function IncidentAlerts({ incidentId }: Props) { id: "remove", header: "", cell: (context) => ( - + incident.is_confirmed && + ), }), ]; diff --git a/keep-ui/app/incidents/[id]/incident-info.tsx b/keep-ui/app/incidents/[id]/incident-info.tsx index a3c4fa612f..35c1a28774 100644 --- a/keep-ui/app/incidents/[id]/incident-info.tsx +++ b/keep-ui/app/incidents/[id]/incident-info.tsx @@ -3,8 +3,11 @@ import { IncidentDto } from "../model"; import CreateOrUpdateIncident from "../create-or-update-incident"; import Modal from "@/components/ui/Modal"; import React, {useState} from "react"; -import {MdModeEdit} from "react-icons/md"; +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 { RiSparkling2Line } from "react-icons/ri"; interface Props { @@ -12,6 +15,8 @@ interface Props { } export default function IncidentInformation({ incident }: Props) { + const router = useRouter(); + const { data: session } = useSession(); const { mutate } = useIncident(incident.id); const [isFormOpen, setIsFormOpen] = useState(false); @@ -32,18 +37,52 @@ export default function IncidentInformation({ incident }: Props) {
- ⚔️ Incident Information - +
+ }
{incident.name}

Description: {incident.description}

diff --git a/keep-ui/app/incidents/[id]/incident.tsx b/keep-ui/app/incidents/[id]/incident.tsx index f41bf2ae2e..70ad63f3b9 100644 --- a/keep-ui/app/incidents/[id]/incident.tsx +++ b/keep-ui/app/incidents/[id]/incident.tsx @@ -62,7 +62,7 @@ export default function IncidentView({ incidentId }: Props) { - + Coming Soon... Coming Soon... diff --git a/keep-ui/app/incidents/incident-candidate-actions.tsx b/keep-ui/app/incidents/incident-candidate-actions.tsx new file mode 100644 index 0000000000..e6170dd095 --- /dev/null +++ b/keep-ui/app/incidents/incident-candidate-actions.tsx @@ -0,0 +1,53 @@ +import {getApiURL} from "../../utils/apiUrl"; +import {toast} from "react-toastify"; +import {IncidentDto, PaginatedIncidentsDto} from "./model"; +import {Session} from "next-auth"; + +interface Props { + incidentId: string; + mutate: () => void; + session: Session | null; +} + +export const handleConfirmPredictedIncident = async ({incidentId, mutate, session}: Props) => { + const apiUrl = getApiURL(); + const response = await fetch( + `${apiUrl}/incidents/${incidentId}/confirm`, + { + method: "POST", + headers: { + Authorization: `Bearer ${session?.accessToken}`, + "Content-Type": "application/json", + }, + } + ); + if (response.ok) { + await mutate(); + toast.success("Predicted incident confirmed successfully"); + } else { + toast.error( + "Failed to confirm predicted incident, please contact us if this issue persists." + ); + } +} + +export const deleteIncident = async ({incidentId, mutate, session}: Props) => { + const apiUrl = getApiURL(); + if (confirm("Are you sure you want to delete this incident?")) { + const response = await fetch(`${apiUrl}/incidents/${incidentId}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${session?.accessToken}`, + }, + }) + + if (response.ok) { + await mutate(); + toast.success("Incident deleted successfully"); + return true + } else { + toast.error("Failed to delete incident, contact us if this persists"); + return false + } + } +}; diff --git a/keep-ui/app/incidents/incident-table-component.tsx b/keep-ui/app/incidents/incident-table-component.tsx new file mode 100644 index 0000000000..8d0d239d74 --- /dev/null +++ b/keep-ui/app/incidents/incident-table-component.tsx @@ -0,0 +1,65 @@ +import {Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow} from "@tremor/react"; +import { flexRender, Table as ReactTable } from "@tanstack/react-table"; +import React from "react"; +import { IncidentDto } from "./model"; +import { useRouter } from "next/navigation"; + +interface Props { + table: ReactTable; +} + +export const IncidentTableComponent = (props: Props) => { + + const { table } = props; + + const router = useRouter(); + + return ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows.map((row) => ( + <> + { + router.push(`/incidents/${row.original.id}`); + }} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + + ))} + +
+ ) + +} + +export default IncidentTableComponent; \ No newline at end of file diff --git a/keep-ui/app/incidents/incidents-table.tsx b/keep-ui/app/incidents/incidents-table.tsx index 1450acd376..7e8e93ee38 100644 --- a/keep-ui/app/incidents/incidents-table.tsx +++ b/keep-ui/app/incidents/incidents-table.tsx @@ -1,30 +1,22 @@ import { Button, - Table, - TableBody, - TableCell, - TableHead, - TableHeaderCell, - TableRow, Badge, } from "@tremor/react"; import { DisplayColumnDef, ExpandedState, createColumnHelper, - flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; import { MdRemoveCircle, MdModeEdit } from "react-icons/md"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; -import { toast } from "react-toastify"; import {IncidentDto, PaginatedIncidentsDto} from "./model"; import React, {Dispatch, SetStateAction, useEffect, useState} from "react"; -import { useRouter } from "next/navigation"; import Image from "next/image"; import IncidentPagination from "./incident-pagination"; +import IncidentTableComponent from "./incident-table-component"; +import {deleteIncident} from "./incident-candidate-actions"; const columnHelper = createColumnHelper(); @@ -41,7 +33,6 @@ export default function IncidentsTable({ setPagination, editCallback, }: Props) { - const router = useRouter(); const { data: session } = useSession(); const [expanded, setExpanded] = useState({}); const [pagination, setTablePagination] = useState({ @@ -151,10 +142,10 @@ export default function IncidentsTable({ size="xs" variant="secondary" icon={MdRemoveCircle} - onClick={(e: React.MouseEvent) => { + onClick={async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - deleteIncident(context.row.original.id!); + await deleteIncident({incidentId: context.row.original.id!, mutate, session}); }} />
@@ -173,70 +164,9 @@ export default function IncidentsTable({ onExpandedChange: setExpanded, }); - const deleteIncident = (incidentId: string) => { - const apiUrl = getApiURL(); - if (confirm("Are you sure you want to delete this incident?")) { - fetch(`${apiUrl}/incidents/${incidentId}`, { - method: "DELETE", - headers: { - Authorization: `Bearer ${session?.accessToken}`, - }, - }).then((response) => { - if (response.ok) { - mutate(); - toast.success("Incident deleted successfully"); - } else { - toast.error("Failed to delete incident, contact us if this persists"); - } - }); - } - }; - return (
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ); - })} - - ))} - - - {table.getRowModel().rows.map((row) => ( - <> - { - router.push(`/incidents/${row.original.id}`); - }} - > - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - - ))} - -
+
diff --git a/keep-ui/app/incidents/model.ts b/keep-ui/app/incidents/model.ts index 7a798fadd2..dd22ed1beb 100644 --- a/keep-ui/app/incidents/model.ts +++ b/keep-ui/app/incidents/model.ts @@ -12,6 +12,8 @@ export interface IncidentDto { start_time?: Date; end_time?: Date; creation_time: Date; + is_confirmed: boolean; + is_predicted: boolean; } export interface PaginatedIncidentsDto { diff --git a/keep-ui/app/incidents/predicted-incidents-table.tsx b/keep-ui/app/incidents/predicted-incidents-table.tsx index f625713396..0cc8d9758e 100644 --- a/keep-ui/app/incidents/predicted-incidents-table.tsx +++ b/keep-ui/app/incidents/predicted-incidents-table.tsx @@ -1,28 +1,21 @@ import { Button, - Table, - TableBody, - TableCell, - TableHead, - TableHeaderCell, - TableRow, Badge } from "@tremor/react"; import { DisplayColumnDef, ExpandedState, createColumnHelper, - flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table"; import { MdDone, MdBlock} from "react-icons/md"; import { useSession } from "next-auth/react"; -import { getApiURL } from "utils/apiUrl"; -import { toast } from "react-toastify"; import {IncidentDto, PaginatedIncidentsDto} from "./model"; import React, { useState } from "react"; import Image from "next/image"; +import { IncidentTableComponent } from "./incident-table-component"; +import {deleteIncident, handleConfirmPredictedIncident} from "./incident-candidate-actions"; const columnHelper = createColumnHelper(); @@ -40,28 +33,6 @@ export default function PredictedIncidentsTable({ const { data: session } = useSession(); const [expanded, setExpanded] = useState({}); - const handleConfirmPredictedIncident = async (incidentId: string) => { - const apiUrl = getApiURL(); - const response = await fetch( - `${apiUrl}/incidents/${incidentId}/confirm`, - { - method: "POST", - headers: { - Authorization: `Bearer ${session?.accessToken}`, - "Content-Type": "application/json", - }, - } - ); - if (response.ok) { - await mutate(); - toast.success("Predicted incident confirmed successfully"); - } else { - toast.error( - "Failed to confirm predicted incident, please contact us if this issue persists." - ); - } - } - const columns = [ columnHelper.display({ id: "name", @@ -73,18 +44,6 @@ export default function PredictedIncidentsTable({ header: "Description", cell: ({ row }) =>
{row.original.description}
, }), - // columnHelper.display({ - // id: "severity", - // header: "Severity", - // cell: (context) => { - // const severity = context.row.original.severity; - // let color; - // if (severity === "critical") color = "red"; - // else if (severity === "info") color = "blue"; - // else if (severity === "warning") color = "yellow"; - // return {severity}; - // }, - // }), columnHelper.display({ id: "alert_count", header: "Number of Alerts", @@ -126,10 +85,10 @@ export default function PredictedIncidentsTable({ tooltip="Confirm incident" variant="secondary" icon={MdDone} - onClick={(e: React.MouseEvent) => { + onClick={async (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); - handleConfirmPredictedIncident(context.row.original.id!); + await handleConfirmPredictedIncident({incidentId: context.row.original.id!, mutate, session}); }} />
@@ -157,65 +116,7 @@ export default function PredictedIncidentsTable({ onExpandedChange: setExpanded, }); - const deleteIncident = (incidentFingerprint: string) => { - const apiUrl = getApiURL(); - if (confirm("Are you sure you want to delete this incident?")) { - fetch(`${apiUrl}/incidents/${incidentFingerprint}`, { - method: "DELETE", - headers: { - Authorization: `Bearer ${session?.accessToken}`, - }, - }).then((response) => { - if (response.ok) { - mutate(); - toast.success("Incident deleted successfully"); - } else { - toast.error("Failed to delete incident, contact us if this persists"); - } - }); - } - }; - return ( - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ); - })} - - ))} - - - {table.getRowModel().rows.map((row) => ( - <> - - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - - ))} - -
- ); + + return ; } diff --git a/keep/api/models/alert.py b/keep/api/models/alert.py index e47e2635a3..69b4a58206 100644 --- a/keep/api/models/alert.py +++ b/keep/api/models/alert.py @@ -363,6 +363,7 @@ class IncidentDto(IncidentDtoIn): services: list[str] is_predicted: bool + is_confirmed: bool generated_summary: str | None @@ -388,6 +389,7 @@ def from_db_incident(cls, db_incident): name=db_incident.name, description=db_incident.description, is_predicted=db_incident.is_predicted, + is_confirmed=db_incident.is_confirmed, creation_time=db_incident.creation_time, start_time=db_incident.start_time, end_time=db_incident.end_time,