Skip to content

Commit

Permalink
feat: Provider Health functionality (keephq#3169)
Browse files Browse the repository at this point in the history
  • Loading branch information
VladimirFilonov authored Jan 30, 2025
1 parent 940fceb commit 8fc68ca
Show file tree
Hide file tree
Showing 31 changed files with 761 additions and 168 deletions.
164 changes: 164 additions & 0 deletions keep-ui/app/(health)/health/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React from "react";
import Modal from "@/components/ui/Modal";
import { useApi } from "@/shared/lib/hooks/useApi";
import {
Badge,
BarChart,
Button,
Card,
DonutChart,
Subtitle,
Title,
} from "@tremor/react";
import { CheckCircle2Icon } from "lucide-react";

interface ProviderHealthResultsModalProps {
handleClose: () => void;
isOpen: boolean;
healthResults: any;
}

const ProviderHealthResultsModal = ({
handleClose,
isOpen,
healthResults,
}: ProviderHealthResultsModalProps) => {
const api = useApi();

const handleModalClose = () => {
handleClose();
};

return (
<Modal
isOpen={isOpen}
onClose={handleModalClose}
title="Health Results"
className="w-[50%] max-w-full"
>
<div className="relative bg-white p-6 rounded-lg">
<div className="grid grid-cols-2 cols-2 gap-4">
<Card className="text-center flex flex-col justify-between">
<Title>Spammy Alerts</Title>
{healthResults?.spammy?.length ? (
<>
<BarChart
className="mx-auto"
data={healthResults.spammy}
categories={["value"]}
index={"date"}
xAxisLabel={"Timestamp"}
showXAxis={false}
colors={["red"]}
showAnimation={true}
showLegend={false}
showGridLines={true}
/>
Sorry to say, but looks like your alerts are spammy
</>
) : (
<>
<div className="flex justify-center pt-4 pb-2">
<CheckCircle2Icon color="green" />
</div>
<Subtitle>Everything is ok</Subtitle>
</>
)}
</Card>
<Card className="text-center flex flex-col justify-between">
<Title>Rules Quality</Title>
{healthResults?.rules?.unused ? (
<>
<DonutChart
data={[
{ name: "used", value: healthResults.rules.used },
{ name: "unused", value: healthResults.rules.unused },
]}
showAnimation={true}
showLabel={false}
colors={["green", "red"]}
/>
<Subtitle>
{healthResults?.rules.unused} of your{" "}
{healthResults.rules.used + healthResults.rules.unused} alert
rules are not in use
</Subtitle>
</>
) : (
<>
<div className="flex justify-center pt-4 pb-2">
<CheckCircle2Icon color="green" />
</div>
<Subtitle>Everything is ok</Subtitle>
</>
)}
</Card>
<Card className="text-center flex flex-col justify-between">
<Title>Actionable</Title>
<div className="flex justify-center pt-4 pb-2">
<CheckCircle2Icon color="green" />
</div>
<Subtitle>Everything is ok</Subtitle>
</Card>

<Card className="text-center flex flex-col justify-between">
<Title>Topology coverage</Title>
{healthResults?.topology?.uncovered.length ? (
<>
<DonutChart
data={[
{
name: "covered",
value: healthResults.topology.covered.length,
},
{
name: "uncovered",
value: healthResults.topology.uncovered.length,
},
]}
showAnimation={true}
showLabel={false}
colors={["green", "red"]}
/>
<Subtitle>
Not of your services are covered. Alerts are missing for:
{healthResults?.topology?.uncovered.map((service: any) => {
return (
<Badge key={service.service} className="mr-1">
{service.display_name
? service.display_name
: service.service}
</Badge>
);
})}
</Subtitle>
</>
) : (
<>
<div className="flex justify-center pt-4 pb-2">
<CheckCircle2Icon color="green" />
</div>
<Subtitle>Everything is ok</Subtitle>
</>
)}
</Card>
</div>

<Title className="text-center pt-10 pb-5">
Want to improve your observability?
</Title>
<Button
size="lg"
color="orange"
variant="primary"
className="w-full"
onClick={() => window.open(`https://platform.keephq.dev/providers`)}
>
Sign up to Keep
</Button>
</div>
</Modal>
);
};

export default ProviderHealthResultsModal;
62 changes: 62 additions & 0 deletions keep-ui/app/(health)/health/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"use client";

import ProvidersTiles from "@/app/(keep)/providers/providers-tiles";
import React, { useEffect, useState } from "react";
import { defaultProvider, Provider } from "@/app/(keep)/providers/providers";
import { useProvidersWithHealthCheck } from "@/utils/hooks/useProviders";
import Loading from "@/app/(keep)/loading";

const useFetchProviders = () => {
const [providers, setProviders] = useState<Provider[]>([]);
const { data, error, mutate } = useProvidersWithHealthCheck();

if (error) {
throw error;
}

const isLocalhost: boolean = true;

useEffect(() => {
if (data) {
const fetchedProviders = data.providers
.filter((provider: Provider) => {
return provider.health;
})
.map((provider) => ({
...defaultProvider,
...provider,
id: provider.type,
installed: provider.installed ?? false,
health: provider.health,
}));

setProviders(fetchedProviders);
}
}, [data]);

return {
providers,
error,
isLocalhost,
mutate,
};
};

export default function ProviderHealthPage () {
const { providers, isLocalhost, mutate } = useFetchProviders();

if (!providers || providers.length <= 0) {
return <Loading />;
}

return (
<>
<ProvidersTiles
providers={providers}
isLocalhost={isLocalhost}
isHealthCheck={true}
mutate={mutate}
/>
</>
);
}
72 changes: 72 additions & 0 deletions keep-ui/app/(health)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { ReactNode } from "react";
import { NextAuthProvider } from "../auth-provider";
import { Mulish } from "next/font/google";
import { ToastContainer } from "react-toastify";
import Navbar from "components/navbar/Navbar";
import { TopologyPollingContextProvider } from "@/app/(keep)/topology/model/TopologyPollingContext";
import { FrigadeProvider } from "../frigade-provider";
import { getConfig } from "@/shared/lib/server/getConfig";
import { ConfigProvider } from "../config-provider";
import { PHProvider } from "../posthog-provider";
import dynamic from "next/dynamic";
import ReadOnlyBanner from "../read-only-banner";
import { auth } from "@/auth";
import { ThemeScript, WatchUpdateTheme } from "@/shared/ui";
import "@/app/globals.css";
import "react-toastify/dist/ReactToastify.css";

const PostHogPageView = dynamic(() => import("@/shared/ui/PostHogPageView"), {
ssr: false,
});

// If loading a variable font, you don't need to specify the font weight
const mulish = Mulish({
subsets: ["latin"],
display: "swap",
});

type RootLayoutProps = {
children: ReactNode;
};

export default async function RootLayout({ children }: RootLayoutProps) {
const config = getConfig();
const session = await auth();

return (
<html lang="en" className={`bg-gray-50 ${mulish.className}`}>
<body className="h-screen flex flex-col lg:grid lg:grid-cols-[fit-content(250px)_30px_auto] lg:grid-rows-1 lg:has-[aside[data-minimized='true']]:grid-cols-[0px_30px_auto]">
{/* ThemeScript must be the first thing to avoid flickering */}
<ThemeScript />
<ConfigProvider config={config}>
<PHProvider>
<NextAuthProvider session={session}>
<FrigadeProvider>
{/* @ts-ignore-error Server Component */}
<PostHogPageView />
{/* https://discord.com/channels/752553802359505017/1068089513253019688/1117731746922893333 */}
<main className="page-container flex flex-col col-start-3 overflow-auto">
{/* Add the banner here, before the navbar */}
{config.READ_ONLY && <ReadOnlyBanner />}
<div className="flex-1">{children}</div>
<ToastContainer />
</main>
</FrigadeProvider>
</NextAuthProvider>
</PHProvider>
</ConfigProvider>
<WatchUpdateTheme />

{/** footer */}
{process.env.GIT_COMMIT_HASH &&
process.env.SHOW_BUILD_INFO !== "false" && (
<div className="pointer-events-none fixed right-2.5 bottom-2.5 text-slate-400 opacity-80 text-xs">
Build: {process.env.GIT_COMMIT_HASH}
<br />
Version: {process.env.KEEP_VERSION}
</div>
)}
</body>
</html>
);
}
2 changes: 1 addition & 1 deletion keep-ui/app/(keep)/ai/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface AIConfig {
algorithm: {
name: string;
description: string;
}
};
}

export interface AIStats {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const CreateIncidentWithAIModal = ({
20,
0,
{ id: "creation_time", desc: true },
'',
"",
{}
);

Expand Down
2 changes: 1 addition & 1 deletion keep-ui/app/(keep)/alerts/alert-table-facet-value.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const FacetValue: React.FC<FacetValueProps> = ({
100,
undefined,
undefined,
'',
"",
{
revalidateOnFocus: false,
}
Expand Down
4 changes: 2 additions & 2 deletions keep-ui/app/(keep)/alerts/alerts-rules-builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -504,8 +504,8 @@ export const AlertsRulesBuilder = ({
operators: getOperators(id),
}))
: customFields
? customFields
: [];
? customFields
: [];

const onImportSQL = () => {
setImportSQLOpen(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ export const DeduplicationPlaceholder = () => {
<div className="text-center space-y-3">
<Title className="text-2xl">No Deduplications Yet</Title>
<Subtitle className="text-gray-400">
Alert deduplication is the first layer of denoising. It groups similar alerts from one source.<br /> To connect alerts across sources into incidents, check <a href="/rules" className="underline text-orange-500">Correlations</a>
Alert deduplication is the first layer of denoising. It groups
similar alerts from one source.
<br /> To connect alerts across sources into incidents, check{" "}
<a href="/rules" className="underline text-orange-500">
Correlations
</a>
</Subtitle>
<Subtitle className="text-gray-400">
This page will become active once the first alerts are registered.
Expand Down
12 changes: 7 additions & 5 deletions keep-ui/app/(keep)/deduplication/DeduplicationTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ export const DeduplicationTable: React.FC<DeduplicationTableProps> = ({
"Represents the percentage of alerts successfully deduplicated. Higher values indicate better deduplication efficiency, meaning fewer redundant alerts.",
};

function resolveDeleteButtonTooltip(deduplicationRule: DeduplicationRule): string {
function resolveDeleteButtonTooltip(
deduplicationRule: DeduplicationRule
): string {
if (deduplicationRule.default) {
return "Cannot delete default rule";
}
Expand All @@ -129,7 +131,7 @@ export const DeduplicationTable: React.FC<DeduplicationTableProps> = ({
return "Cannot delete provisioned rule.";
}

return "Delete Rule"
return "Delete Rule";
}

const DEDUPLICATION_TABLE_COLS = useMemo(
Expand Down Expand Up @@ -276,10 +278,10 @@ export const DeduplicationTable: React.FC<DeduplicationTableProps> = ({
size="xs"
variant="secondary"
icon={TrashIcon}
tooltip={
resolveDeleteButtonTooltip(info.row.original)
tooltip={resolveDeleteButtonTooltip(info.row.original)}
disabled={
info.row.original.default || info.row.original.is_provisioned
}
disabled={info.row.original.default || info.row.original.is_provisioned}
onClick={(e) => handleDeleteRule(info.row.original, e)}
/>
</div>
Expand Down
Loading

0 comments on commit 8fc68ca

Please sign in to comment.