Skip to content

Commit

Permalink
feat: Add predicted incident detail page (#1573)
Browse files Browse the repository at this point in the history
  • Loading branch information
VladimirFilonov committed Aug 9, 2024
1 parent 0b287bf commit 2b0835d
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 204 deletions.
18 changes: 10 additions & 8 deletions keep-ui/app/incidents/[id]/incident-alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -41,13 +42,13 @@ interface Pagination {

const columnHelper = createColumnHelper<AlertDto>();

export default function IncidentAlerts({ incidentId }: Props) {
export default function IncidentAlerts({ incident }: Props) {
const [alertsPagination, setAlertsPagination] = useState<Pagination>({
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,
Expand All @@ -69,7 +70,7 @@ export default function IncidentAlerts({ incidentId }: Props) {
})
}
}, [pagination])
usePollIncidentAlerts(incidentId);
usePollIncidentAlerts(incident.id);

const columns = [
columnHelper.accessor("severity", {
Expand Down Expand Up @@ -128,10 +129,11 @@ export default function IncidentAlerts({ incidentId }: Props) {
id: "remove",
header: "",
cell: (context) => (
<IncidentAlertMenu
alert={context.row.original}
incidentId={incidentId}
/>
incident.is_confirmed &&
<IncidentAlertMenu
alert={context.row.original}
incidentId={incident.id}
/>
),
}),
];
Expand Down
65 changes: 52 additions & 13 deletions keep-ui/app/incidents/[id]/incident-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@ 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 {
incident: IncidentDto;
}

export default function IncidentInformation({ incident }: Props) {
const router = useRouter();
const { data: session } = useSession();
const { mutate } = useIncident(incident.id);
const [isFormOpen, setIsFormOpen] = useState<boolean>(false);

Expand All @@ -32,18 +37,52 @@ export default function IncidentInformation({ incident }: Props) {
<div className="flex h-full flex-col justify-between">
<div>
<div className="flex justify-between mb-2.5">
<Title className="">⚔️ Incident Information</Title>
<Button
color="orange"
size="xs"
variant="secondary"
icon={MdModeEdit}
onClick={(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
handleStartEdit();
}}
/>
<Title className="">{incident.is_confirmed ? "⚔️ " : "Possible "}Incident Information</Title>
{incident.is_confirmed &&
<Button
color="orange"
size="xs"
variant="secondary"
icon={MdModeEdit}
onClick={(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
handleStartEdit();
}}
/>
}
{!incident.is_confirmed &&
<div className={"space-x-1 flex flex-row items-center justify-center"}>
<Button
color="orange"
size="xs"
tooltip="Confirm incident"
variant="secondary"
title="Confirm"
icon={MdDone}
onClick={(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
handleConfirmPredictedIncident({incidentId: incident.id!, mutate, session});
}}
>Confirm</Button>
<Button
color="red"
size="xs"
variant="secondary"
tooltip={"Discard"}
icon={MdBlock}
onClick={async (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
const success = await deleteIncident({incidentId: incident.id!, mutate, session});
if (success) {
router.push("/incidents");
}
}}
/>
</div>
}
</div>
<div className="prose-2xl">{incident.name}</div>
<p>Description: {incident.description}</p>
Expand Down
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 @@ -62,7 +62,7 @@ export default function IncidentView({ incidentId }: Props) {
</TabList>
<TabPanels>
<TabPanel>
<IncidentAlerts incidentId={incident.id} />
<IncidentAlerts incident={incident} />
</TabPanel>
<TabPanel>Coming Soon...</TabPanel>
<TabPanel>Coming Soon...</TabPanel>
Expand Down
53 changes: 53 additions & 0 deletions keep-ui/app/incidents/incident-candidate-actions.tsx
Original file line number Diff line number Diff line change
@@ -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
}
}
};
65 changes: 65 additions & 0 deletions keep-ui/app/incidents/incident-table-component.tsx
Original file line number Diff line number Diff line change
@@ -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<IncidentDto>;
}

export const IncidentTableComponent = (props: Props) => {

const { table } = props;

const router = useRouter();

return (
<Table className="mt-4">
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow
className="border-b border-tremor-border dark:border-dark-tremor-border"
key={headerGroup.id}
>
{headerGroup.headers.map((header) => {
return (
<TableHeaderCell
className="text-tremor-content-strong dark:text-dark-tremor-content-strong"
key={header.id}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHeaderCell>
);
})}
</TableRow>
))}
</TableHead>
<TableBody>
{table.getRowModel().rows.map((row) => (
<>
<TableRow
className="even:bg-tremor-background-muted even:dark:bg-dark-tremor-background-muted hover:bg-slate-100 cursor-pointer"
key={row.id}
onClick={() => {
router.push(`/incidents/${row.original.id}`);
}}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
</>
))}
</TableBody>
</Table>
)

}

export default IncidentTableComponent;
80 changes: 5 additions & 75 deletions keep-ui/app/incidents/incidents-table.tsx
Original file line number Diff line number Diff line change
@@ -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<IncidentDto>();

Expand All @@ -41,7 +33,6 @@ export default function IncidentsTable({
setPagination,
editCallback,
}: Props) {
const router = useRouter();
const { data: session } = useSession();
const [expanded, setExpanded] = useState<ExpandedState>({});
const [pagination, setTablePagination] = useState({
Expand Down Expand Up @@ -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});
}}
/>
</div>
Expand All @@ -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 (
<div>
<Table className="mt-4">
<TableHead>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow
className="border-b border-tremor-border dark:border-dark-tremor-border"
key={headerGroup.id}
>
{headerGroup.headers.map((header) => {
return (
<TableHeaderCell
className="text-tremor-content-strong dark:text-dark-tremor-content-strong"
key={header.id}
>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHeaderCell>
);
})}
</TableRow>
))}
</TableHead>
<TableBody>
{table.getRowModel().rows.map((row) => (
<>
<TableRow
className="even:bg-tremor-background-muted even:dark:bg-dark-tremor-background-muted hover:bg-slate-100 cursor-pointer"
key={row.id}
onClick={() => {
router.push(`/incidents/${row.original.id}`);
}}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
</>
))}
</TableBody>
</Table>
<IncidentTableComponent table={table} />
<div className="mt-4 mb-8">
<IncidentPagination table={table} isRefreshAllowed={true}/>
</div>
Expand Down
Loading

0 comments on commit 2b0835d

Please sign in to comment.