Skip to content

Commit

Permalink
Merge branch 'main' into Feat/runbook
Browse files Browse the repository at this point in the history
  • Loading branch information
Mubashirshariq authored Oct 7, 2024
2 parents 12bdd12 + 36f2beb commit 2988810
Show file tree
Hide file tree
Showing 29 changed files with 2,777 additions and 1,594 deletions.
4 changes: 2 additions & 2 deletions STRESS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

# UNDER CONSTRUCTION

# First, create a kubernetes cluster
# First, create a Kubernetes cluster


# Install Keep
Expand Down Expand Up @@ -35,7 +35,7 @@ kubectl -n keep exec -it keep-database-659c69689-vxhkz -- bash -c "mysql -u roo

# No Load
## 500k alerts - 1Gi/250m cpu: get_last_alerts 2 minutes and 30 seconds
- Keep Backend Workers get timeout after a one minutes (500's for preset and alert endpoints)
Keep Backend Workers get a timeout after one minute (status code 500 for preset and alert endpoints)
## 500k alerts - 2Gi/500m cpu:
- default mysql: get_last_alerts 1 minutes and 30 seconds
- innodb_buffer_pool_size = 4294967296: 25 seconds, 3 seconds after cache
Expand Down
13 changes: 7 additions & 6 deletions keep-ui/app/alerts/alert-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { Dialog, Transition } from "@headlessui/react";
import { AlertDto } from "./models";
import { Button, Title, Card, Badge } from "@tremor/react";
import { IoMdClose } from "react-icons/io";
// import AlertMenu from "./alert-menu";
import AlertTimeline from "./alert-timeline";
import { useAlerts } from "utils/hooks/useAlerts";
import { TopologyMap } from "../topology/ui/map";
import { TopologySearchProvider } from "@/app/topology/TopologySearchContext";

type AlertSidebarProps = {
isOpen: boolean;
Expand Down Expand Up @@ -108,11 +108,12 @@ const AlertSidebar = ({ isOpen, toggle, alert }: AlertSidebarProps) => {
/>
</Card>
<Title>Related Services</Title>
<TopologyMap
providerId={alert.providerId || ""}
service={alert.service || ""}
environment={"unknown"}
/>
<TopologySearchProvider>
<TopologyMap
providerIds={alert.providerId ? [alert.providerId] : []}
services={alert.service ? [alert.service] : []}
/>
</TopologySearchProvider>
</div>
)}
</Dialog.Panel>
Expand Down
4 changes: 3 additions & 1 deletion keep-ui/app/incidents/[id]/incident-chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export default function IncidentChat({ incident }: { incident: IncidentDto }) {
incident.id,
name,
summary,
incident.assignee
incident.assignee,
true
);
if (response.ok) {
mutate();
Expand Down Expand Up @@ -109,6 +110,7 @@ export default function IncidentChat({ incident }: { incident: IncidentDto }) {
"rgb(249 115 22 / var(--tw-bg-opacity))",
} as CopilotKitCSSProperties
}
className="max-w-3xl mx-auto"
>
<CopilotChat
className="-mx-2"
Expand Down
60 changes: 55 additions & 5 deletions keep-ui/app/incidents/[id]/incident-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ 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 { useIncident } from "@/utils/hooks/useIncidents";
import {
deleteIncident,
handleConfirmPredictedIncident,
Expand All @@ -13,13 +13,57 @@ import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { format } from "date-fns";
import { ArrowUturnLeftIcon } from "@heroicons/react/24/outline";
import { Disclosure } from "@headlessui/react";
import classNames from "classnames";
import { IoChevronDown } from "react-icons/io5";
import IncidentChangeStatusModal from "@/app/incidents/incident-change-status-modal";
import {STATUS_ICONS} from "@/app/incidents/statuses";

interface Props {
incident: IncidentDto;
}

function Summary({
title,
summary,
collapsable,
className,
}: {
title: string;
summary: string;
collapsable?: boolean;
className?: string;
}) {
if (collapsable) {
return (
<Disclosure as="div" className={classNames("space-y-1", className)}>
<Disclosure.Button>
{({ open }) => (
<h4 className="text-gray-500 text-sm inline-flex justify-between items-center gap-1">
<span>{title}</span>
<IoChevronDown
className={classNames({ "rotate-180": open }, "text-slate-400")}
/>
</h4>
)}
</Disclosure.Button>

<Disclosure.Panel as="div" className="space-y-2 relative">
{summary}
</Disclosure.Panel>
</Disclosure>
);
}

return (
<div className={className}>
<h3 className="text-gray-500 text-sm">{title}</h3>
{/*TODO: suggest generate summary if it's empty*/}
{summary ? <p>{summary}</p> : <p>No summary yet</p>}
</div>
);
}

export default function IncidentInformation({ incident }: Props) {
const router = useRouter();
const { data: session } = useSession();
Expand Down Expand Up @@ -58,7 +102,7 @@ export default function IncidentInformation({ incident }: Props) {
else if (severity === "warning") severityColor = "yellow";

return (
<div className="flex h-full flex-col justify-between">
<div className="flex w-full h-full flex-col justify-between">
<div className="flex flex-col gap-2">
<div className="flex justify-between text-sm gap-1">
<Title className="flex-grow items-center">
Expand Down Expand Up @@ -141,9 +185,15 @@ export default function IncidentInformation({ incident }: Props) {
</div>
</div>
</div>
<div>
<h3 className="text-gray-500 text-sm">Summary</h3>
{summary ? <p>{summary}</p> : <p>No summary yet</p>}
<div className="flex flex-col gap-2 max-w-3xl">
<Summary title="Summary" summary={summary} />
{incident.user_summary && incident.generated_summary ? (
<Summary
title="AI version"
summary={incident.generated_summary}
collapsable={true}
/>
) : null}
</div>
<div className="flex gap-4">
{!!incident.start_time && (
Expand Down
28 changes: 18 additions & 10 deletions keep-ui/app/incidents/[id]/incident.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,21 @@ import { useRouter } from "next/navigation";
import IncidentTimeline from "./incident-timeline";
import { CiBellOn, CiChat2, CiViewTimeline } from "react-icons/ci";
import { IoIosGitNetwork } from "react-icons/io";
import { EmptyStateCard } from "@/components/ui/EmptyStateCard";
import IncidentChat from "./incident-chat";
import { Workflows } from "components/icons";
import IncidentWorkflowTable from "./incident-workflow-table";
import { TopologyMap } from "@/app/topology/ui/map";
import { TopologySearchProvider } from "@/app/topology/TopologySearchContext";
import { useState } from "react";

interface Props {
incidentId: string;
}

// TODO: generate metadata with incident name

export default function IncidentView({ incidentId }: Props) {
const { data: incident, isLoading, error } = useIncident(incidentId);
const [index, setIndex] = useState(0);

const router = useRouter();

Expand All @@ -41,7 +44,12 @@ export default function IncidentView({ incidentId }: Props) {
<IncidentInformation incident={incident} />
</div>
<Card className="flex flex-col items-center justify-center gap-y-8 mt-10 p-4 relative mx-auto">
<TabGroup id="incidentTabs" defaultIndex={0}>
<TabGroup
id="incidentTabs"
index={index}
defaultIndex={0}
onIndexChange={setIndex}
>
{/* Compensating for page-container padding, TODO: more robust solution */}
<TabList
variant="line"
Expand Down Expand Up @@ -69,13 +77,13 @@ export default function IncidentView({ incidentId }: Props) {
<TabPanel>
<IncidentTimeline incident={incident} />
</TabPanel>
<TabPanel>
<EmptyStateCard
title="Coming Soon..."
description="Topology view of the incident is coming soon."
buttonText="Go to Topology"
onClick={() => router.push("/topology")}
/>
<TabPanel className="pt-3 h-[calc(100vh-28rem)]">
<TopologySearchProvider>
<TopologyMap
services={incident.services}
isVisible={index === 2}
/>
</TopologySearchProvider>
</TabPanel>
<TabPanel>
<IncidentWorkflowTable incident={incident} />
Expand Down
30 changes: 17 additions & 13 deletions keep-ui/app/incidents/create-or-update-incident.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,25 @@ export const updateIncidentRequest = async (
incidentId: string,
incidentName: string,
incidentUserSummary: string,
incidentAssignee: string
incidentAssignee: string,
generatedByAi: boolean = false
) => {
const apiUrl = getApiURL();
const response = await fetch(`${apiUrl}/incidents/${incidentId}`, {
method: "PUT",
headers: {
Authorization: `Bearer ${session?.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
user_generated_name: incidentName,
user_summary: incidentUserSummary,
assignee: incidentAssignee,
}),
});
const response = await fetch(
`${apiUrl}/incidents/${incidentId}?generatedByAi=${generatedByAi}`,
{
method: "PUT",
headers: {
Authorization: `Bearer ${session?.accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
user_generated_name: incidentName,
user_summary: incidentUserSummary,
assignee: incidentAssignee,
}),
}
);
return response;
};

Expand Down
53 changes: 53 additions & 0 deletions keep-ui/app/topology/TopologySearchContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use client";
import React, { createContext, useContext, useState } from "react";

type TopologySearchContextType = {
selectedObjectId: string | null;
setSelectedObjectId: (id: string | null) => void;
selectedApplicationIds: string[];
setSelectedApplicationIds: (ids: string[]) => void;
};

const defaultContext: TopologySearchContextType = {
selectedObjectId: "",
setSelectedObjectId: () => {},
selectedApplicationIds: [],
setSelectedApplicationIds: () => {},
};

export const TopologySearchContext =
createContext<TopologySearchContextType>(defaultContext);

export function useTopologySearchContext() {
const context = useContext(TopologySearchContext);
if (context === undefined) {
throw new Error(
"useTopologySearchContext must be used within a TopologySearchContextProvider"
);
}
return context;
}

export const TopologySearchProvider: React.FC<{
children: React.ReactNode;
}> = ({ children }) => {
const [selectedServiceId, setSelectedServiceId] = useState<string | null>(
null
);
const [selectedApplicationIds, setSelectedApplicationIds] = useState<
string[]
>([]);

return (
<TopologySearchContext.Provider
value={{
selectedObjectId: selectedServiceId,
setSelectedObjectId: setSelectedServiceId,
selectedApplicationIds,
setSelectedApplicationIds,
}}
>
{children}
</TopologySearchContext.Provider>
);
};
48 changes: 22 additions & 26 deletions keep-ui/app/topology/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
import { getApiURL } from "../../../utils/apiUrl";
import { fetcher } from "../../../utils/fetcher";
import { getApiURL } from "@/utils/apiUrl";
import { fetcher } from "@/utils/fetcher";
import { Session } from "next-auth";
import { TopologyApplication, TopologyService } from "../model/models";

const isNullOrUndefined = (value: unknown): value is null | undefined =>
value === null || value === undefined;

export function buildTopologyUrl({
providerId,
service,
providerIds,
services,
environment,
}: {
providerId?: string;
service?: string;
providerIds?: string[];
services?: string[];
environment?: string;
}) {
const apiUrl = getApiURL();

const baseUrl = `${apiUrl}/topology`;

if (
!isNullOrUndefined(providerId) &&
!isNullOrUndefined(service) &&
!isNullOrUndefined(environment)
) {
const params = new URLSearchParams({
provider_id: providerId,
service_id: service,
environment: environment,
});
return `${baseUrl}?${params.toString()}`;
const params = new URLSearchParams();

if (providerIds) {
params.append("provider_ids", providerIds.join(","));
}
if (services) {
params.append("services", services.join(","));
}
if (environment) {
params.append("environment", environment);
}

return baseUrl;
return `${baseUrl}?${params.toString()}`;
}

export async function getApplications(session: Session | null) {
Expand All @@ -48,18 +44,18 @@ export async function getApplications(session: Session | null) {
export function getTopology(
session: Session | null,
{
providerId,
service,
providerIds,
services,
environment,
}: {
providerId?: string;
service?: string;
providerIds?: string[];
services?: string[];
environment?: string;
}
) {
if (!session) {
return null;
}
const url = buildTopologyUrl({ providerId, service, environment });
const url = buildTopologyUrl({ providerIds, services, environment });
return fetcher(url, session.accessToken) as Promise<TopologyService[]>;
}
Loading

0 comments on commit 2988810

Please sign in to comment.