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: Add predicted incident detail page #1573

Merged
merged 1 commit into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading