Skip to content

Commit

Permalink
feat: wip
Browse files Browse the repository at this point in the history
  • Loading branch information
talboren committed Nov 24, 2024
1 parent cda1363 commit 944af05
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 27 deletions.
12 changes: 7 additions & 5 deletions keep-ui/app/(keep)/incidents/[id]/alerts/incident-alert-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Icon } from "@tremor/react";
import { Badge, Icon } from "@tremor/react";
import { AlertDto } from "@/app/(keep)/alerts/models";
import { useHydratedSession as useSession } from "@/shared/lib/hooks/useHydratedSession";
import { toast } from "react-toastify";
import { useApiUrl } from "utils/hooks/useConfig";
import { useIncidentAlerts } from "utils/hooks/useIncidents";
import { LinkSlashIcon } from "@heroicons/react/24/outline";
import { LiaUnlinkSolid } from "react-icons/lia";

interface Props {
incidentId: string;
Expand Down Expand Up @@ -44,13 +44,15 @@ export default function IncidentAlertMenu({ incidentId, alert }: Props) {

return (
<div className="flex flex-col">
<Icon
icon={LinkSlashIcon}
<Badge
icon={LiaUnlinkSolid}
color="red"
tooltip="Remove correlation"
className="cursor-pointer"
onClick={onRemove}
/>
>
Unlink
</Badge>
</div>
);
}
8 changes: 2 additions & 6 deletions keep-ui/app/(keep)/incidents/[id]/alerts/incident-alerts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,7 @@ export default function IncidentAlerts({ incident }: Props) {
id: "severity",
header: "Severity",
minSize: 80,
cell: (context) => (
<div className="text-center">
<AlertSeverity severity={context.getValue()} />
</div>
),
cell: (context) => <AlertSeverity severity={context.getValue()} />,
}),
columnHelper.display({
id: "name",
Expand Down Expand Up @@ -161,7 +157,7 @@ export default function IncidentAlerts({ incident }: Props) {
}),
columnHelper.accessor("lastReceived", {
id: "lastReceived",
header: "Last Received",
header: "Last Event Time",
minSize: 100,
cell: (context) => (
<span>{getAlertLastReceieved(context.getValue())}</span>
Expand Down
15 changes: 9 additions & 6 deletions keep-ui/app/(keep)/incidents/[id]/incident-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { IncidentSeverityBadge } from "@/entities/incidents/ui";
import { getIncidentName } from "@/entities/incidents/lib/utils";
import { useIncident } from "@/utils/hooks/useIncidents";
import { IncidentOverview } from "./incident-overview";
import { CopilotKit } from "@copilotkit/react-core";

export function IncidentHeader({
incident: initialIncidentData,
Expand Down Expand Up @@ -53,7 +54,7 @@ export function IncidentHeader({
};

return (
<>
<CopilotKit runtimeUrl="/api/copilotkit">
<header className="flex flex-col gap-4">
<Subtitle className="text-sm">
<Link href="/incidents">All Incidents</Link>{" "}
Expand All @@ -70,26 +71,28 @@ export function IncidentHeader({
size="xs"
variant="secondary"
icon={MdPlayArrow}
tooltip="Run Workflow"
onClick={(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
handleRunWorkflow();
}}
/>
>
Run Workflow
</Button>
{incident.is_confirmed && (
<Button
color="orange"
size="xs"
variant="secondary"
icon={MdModeEdit}
tooltip="Edit Incident"
onClick={(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
handleStartEdit();
}}
/>
>
Edit Incident
</Button>
)}
{!incident.is_confirmed && (
<div className="space-x-1 flex flex-row items-center justify-center">
Expand Down Expand Up @@ -143,6 +146,6 @@ export function IncidentHeader({
incident={runWorkflowModalIncident}
handleClose={() => setRunWorkflowModalIncident(null)}
/>
</>
</CopilotKit>
);
}
140 changes: 130 additions & 10 deletions keep-ui/app/(keep)/incidents/[id]/incident-overview.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
"use client";

import type { IncidentDto } from "@/entities/incidents/model";
import React from "react";
import { useIncident } from "@/utils/hooks/useIncidents";
import {
useIncidentActions,
type IncidentDto,
type PaginatedIncidentAlertsDto,
} from "@/entities/incidents/model";
import React, { useState } from "react";
import { useIncident, useIncidentAlerts } from "@/utils/hooks/useIncidents";
import { Disclosure } from "@headlessui/react";
import { IoChevronDown } from "react-icons/io5";
import remarkRehype from "remark-rehype";
import rehypeRaw from "rehype-raw";
import Markdown from "react-markdown";
import { Badge, Callout } from "@tremor/react";
import { Link } from "@/components/ui";
import { Button, Link } from "@/components/ui";
import { IncidentChangeStatusSelect } from "@/features/change-incident-status";
import { getIncidentName } from "@/entities/incidents/lib/utils";
import { DateTimeField, FieldHeader } from "@/shared/ui";
Expand All @@ -19,6 +23,16 @@ import {
} from "@/features/same-incidents-in-the-past/";
import { StatusIcon } from "@/entities/incidents/ui/statuses";
import clsx from "clsx";
import { TbSparkles } from "react-icons/tb";
import {
CopilotTask,
useCopilotAction,
useCopilotContext,
useCopilotReadable,
} from "@copilotkit/react-core";
import { IncidentOverviewSkeleton } from "../incident-overview-skeleton";
import { AlertDto } from "../../alerts/models";
import { useRouter } from "next/navigation";

interface Props {
incident: IncidentDto;
Expand All @@ -29,15 +43,58 @@ function Summary({
summary,
collapsable,
className,
alerts,
incident,
}: {
title: string;
summary: string;
collapsable?: boolean;
className?: string;
alerts: AlertDto[];
incident: IncidentDto;
}) {
const [generatedSummary, setGeneratedSummary] = useState("");
const { updateIncident } = useIncidentActions();
const context = useCopilotContext();
useCopilotReadable({
description: "The incident alerts",
value: alerts,
});
useCopilotReadable({
description: "The incident title",
value: incident.user_generated_name ?? incident.ai_generated_name,
});
useCopilotAction({
name: "setGeneratedSummary",
description: "Set the generated summary",
parameters: [
{ name: "summary", type: "string", description: "The generated summary" },
],
handler: async ({ summary }) => {
await updateIncident(
incident.id,
{
user_summary: summary,
},
true
);
setGeneratedSummary(summary);
},
});
const task = new CopilotTask({
instructions:
"Generate a short concise summary of the incident based on the context of the alerts and the title of the incident. Don't repeat prompt.",
});
const [generatingSummary, setGeneratingSummary] = useState(false);
const executeTask = async () => {
setGeneratingSummary(true);
await task.run(context);
setGeneratingSummary(false);
};

const formatedSummary = (
<Markdown remarkPlugins={[remarkRehype]} rehypePlugins={[rehypeRaw]}>
{summary}
{summary ?? generatedSummary}
</Markdown>
);

Expand All @@ -62,9 +119,20 @@ function Summary({
);
}

return (
//TODO: suggest generate summary if it's empty
summary ? <div>{formatedSummary}</div> : <p>No summary yet</p>
return summary || generatedSummary ? (
<div>{formatedSummary}</div>
) : (
<Button
variant="secondary"
onClick={executeTask}
className="mt-2.5"
disabled={generatingSummary}
loading={generatingSummary}
icon={TbSparkles}
size="xs"
>
AI Summary
</Button>
);
}

Expand Down Expand Up @@ -104,6 +172,7 @@ function MergedCallout({
}

export function IncidentOverview({ incident: initialIncidentData }: Props) {
const router = useRouter();
const { data: fetchedIncident } = useIncident(initialIncidentData.id, {
fallbackData: initialIncidentData,
revalidateOnMount: false,
Expand All @@ -114,6 +183,28 @@ export function IncidentOverview({ incident: initialIncidentData }: Props) {
const notNullServices = incident.services.filter(
(service) => service !== "null"
);
const {
data: alerts,
isLoading: _alertsLoading,
error: alertsError,
} = useIncidentAlerts(incident.id, 20, 0);
const environments = Array.from(
new Set(
alerts?.items
.filter((alert) => alert.environment)
.map((alert) => alert.environment)
)
);

if (!alerts || _alertsLoading) {
return <IncidentOverviewSkeleton />;
}

const filterBy = (key: string, value: string) => {
router.push(
`/alerts/feed?cel=${key}%3D%3D${encodeURIComponent(`"${value}"`)}`
);
};

return (
// Adding padding bottom to visually separate from the tabs
Expand All @@ -122,12 +213,19 @@ export function IncidentOverview({ incident: initialIncidentData }: Props) {
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
<div className="max-w-2xl">
<FieldHeader>Summary</FieldHeader>
<Summary title="Summary" summary={summary} />
<Summary
title="Summary"
summary={summary}
alerts={alerts.items}
incident={incident}
/>
{incident.user_summary && incident.generated_summary ? (
<Summary
title="AI version"
summary={incident.generated_summary}
collapsable={true}
alerts={alerts.items}
incident={incident}
/>
) : null}
{incident.merged_into_incident_id && (
Expand All @@ -142,14 +240,36 @@ export function IncidentOverview({ incident: initialIncidentData }: Props) {
{notNullServices.length > 0 ? (
<div className="flex flex-wrap gap-1">
{notNullServices.map((service) => (
<Badge key={service} size="sm">
<Badge
key={service}
size="sm"
className="cursor-pointer"
onClick={() => filterBy("service", service)}
>
{service}
</Badge>
))}
</div>
) : (
"No services involved"
)}
<FieldHeader>Affected environments</FieldHeader>
{environments.length > 0 ? (
<div className="flex flex-wrap gap-1">
{environments.map((env) => (
<Badge
key={env}
size="sm"
className="cursor-pointer"
onClick={() => filterBy("environment", env)}
>
{env}
</Badge>
))}
</div>
) : (
"No environments involved"
)}
</div>
<div>
<SameIncidentField incident={incident} />
Expand Down
53 changes: 53 additions & 0 deletions keep-ui/app/(keep)/incidents/incident-overview-skeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Skeleton from "react-loading-skeleton";
import { FieldHeader } from "@/shared/ui";

export function IncidentOverviewSkeleton() {
return (
<div className="flex gap-6 items-start w-full pb-4 text-tremor-default">
<div className="basis-2/3 grow">
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
<div className="max-w-2xl">
<FieldHeader>Summary</FieldHeader>
<Skeleton count={3} />
</div>
<div className="flex flex-col gap-2">
<FieldHeader>Involved services</FieldHeader>
<div className="flex flex-wrap gap-1">
<Skeleton width={80} />
<Skeleton width={100} />
<Skeleton width={90} />
</div>
</div>
<div>
<Skeleton count={2} />
</div>
<div>
<Skeleton count={2} />
</div>
</div>
</div>
<div className="pr-10 grid grid-cols-1 xl:grid-cols-2 gap-4">
<div className="xl:col-span-2">
<FieldHeader>Status</FieldHeader>
<Skeleton height={38} />
</div>
<div>
<FieldHeader>Last Incident Activity</FieldHeader>
<Skeleton />
</div>
<div>
<FieldHeader>Started at</FieldHeader>
<Skeleton />
</div>
<div>
<FieldHeader>Assignee</FieldHeader>
<Skeleton />
</div>
<div>
<FieldHeader>Group by value</FieldHeader>
<Skeleton />
</div>
</div>
</div>
);
}

0 comments on commit 944af05

Please sign in to comment.