Skip to content

Commit

Permalink
Merge branch 'Feat/runbook' into feat/runbook-1780-main-final
Browse files Browse the repository at this point in the history
Signed-off-by: Rajesh Jonnalagadda <[email protected]>
  • Loading branch information
rajesh-jonnalagadda authored Oct 9, 2024
2 parents 5130224 + 40bad4a commit 74bc7d6
Show file tree
Hide file tree
Showing 15 changed files with 674 additions and 97 deletions.
1 change: 0 additions & 1 deletion keep-ui/app/incidents/incident-table-component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const SortableHeaderCell = ({
size="xs"
color="neutral"
onClick={(event) => {
console.log("clicked for sorting");
event.stopPropagation();
const toggleSorting = header.column.getToggleSortingHandler();
if (toggleSorting) toggleSorting(event);
Expand Down
110 changes: 110 additions & 0 deletions keep-ui/app/incidents/incident-table-filters-context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {Dispatch, SetStateAction, useCallback, useContext, useEffect} from 'react';

import { createContext, useState, FC, PropsWithChildren } from "react";
import { useSearchParams, useRouter, usePathname } from "next/navigation";
import {useIncidentsMeta} from "../../utils/hooks/useIncidents";
import {IncidentsMetaDto} from "./models";

interface IIncidentFilterContext {
meta: IncidentsMetaDto | undefined;

statuses: string[];
severities: string[];
assignees: string[];
services: string[];
sources: string[];

setStatuses: (value: string[]) => void;
setSeverities: (value: string[]) => void;
setAssignees: (value: string[]) => void;
setServices: (value: string[]) => void;
setSources: (value: string[]) => void;
}

const IncidentFilterContext = createContext<IIncidentFilterContext | null>(null);

export const IncidentFilterContextProvider: FC<PropsWithChildren> = ({ children }) => {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();

const {data: incidentsMeta, isLoading} = useIncidentsMeta();

const setFilterValue = (filterName: string) => {
return () => {
if (incidentsMeta === undefined) return [];

const values = searchParams?.get(filterName);
const valuesArray = values?.split(',').filter(
value => incidentsMeta[filterName as keyof IncidentsMetaDto]?.includes(value)
);

return (valuesArray || []) as string[];
}
}

const [statuses, setStatuses] = useState<string[]>(setFilterValue("statuses"));
const [severities, setSeverities] = useState<string[]>(setFilterValue("severities"));
const [assignees, setAssignees] = useState<string[]>(setFilterValue("assignees"));
const [services, setServices] = useState<string[]>(setFilterValue("services"));
const [sources, setSources] = useState<string[]>(setFilterValue("sources"));

useEffect(() => {
if (!isLoading) {
setStatuses(setFilterValue("statuses"));
setSeverities(setFilterValue("severities"));
setAssignees(setFilterValue("assignees"));
setServices(setFilterValue("services"));
setSources(setFilterValue("sources"));
}
}, [isLoading])

const createQueryString = useCallback(
(name: string, value: string[]) => {
const params = new URLSearchParams(searchParams?.toString())
if (value.length == 0) {
params.delete(name);
} else {
params.set(name, value.join(","));
}


return params.toString();
},
[searchParams]
)

const filterSetter = (filterName: string, stateSetter: Dispatch<SetStateAction<string[]>>) => {
return (value: string[]) => {
router.push(pathname + '?' + createQueryString(filterName, value));
stateSetter(value);
}
}

const contextValue: IIncidentFilterContext = {
meta: incidentsMeta,
statuses,
severities,
assignees,
services,
sources,

setStatuses: filterSetter("statuses", setStatuses),
setSeverities: filterSetter("severities", setSeverities),
setAssignees: filterSetter("assignees", setAssignees),
setServices: filterSetter("services", setServices),
setSources: filterSetter("sources", setSources),
}

return <IncidentFilterContext.Provider value={contextValue}>{children}</IncidentFilterContext.Provider>
}

export const useIncidentFilterContext = (): IIncidentFilterContext => {
const filterContext = useContext(IncidentFilterContext);

if (!filterContext) {
throw new ReferenceError('Usage of useIncidentFilterContext outside of IncidentFilterContext provider is forbidden');
}

return filterContext;
}
85 changes: 85 additions & 0 deletions keep-ui/app/incidents/incident-table-filters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { FC } from "react";
import { MultiSelect, MultiSelectItem } from "@tremor/react";
import { useIncidentFilterContext } from "./incident-table-filters-context";
import { capitalize } from "@/utils/helpers";

export const IncidentTableFilters: FC = (props) => {
const {
meta,
statuses,
severities,
assignees,
services,
sources,
setStatuses,
setSeverities,
setAssignees,
setServices,
setSources,
} = useIncidentFilterContext();

return (
<div className="flex flex-col md:flex-row gap-2">
{/* TODO: use copy-and-paste multiselect component to be able to control the width */}
<MultiSelect
onValueChange={setStatuses}
value={statuses}
placeholder="Status"
>
{meta?.statuses.map((value) => (
<MultiSelectItem key={value} value={value}>
{capitalize(value)}
</MultiSelectItem>
))}
</MultiSelect>

<MultiSelect
onValueChange={setSeverities}
value={severities}
placeholder="Severity"
>
{meta?.severities.map((value) => (
<MultiSelectItem key={value} value={value}>
{capitalize(value)}
</MultiSelectItem>
))}
</MultiSelect>

<MultiSelect
onValueChange={setAssignees}
value={assignees}
placeholder="Assignee"
>
{meta?.assignees.map((value) => (
<MultiSelectItem key={value} value={value}>
{capitalize(value)}
</MultiSelectItem>
))}
</MultiSelect>

<MultiSelect
onValueChange={setServices}
value={services}
placeholder="Service"
>
{meta?.services.map((value) => (
<MultiSelectItem key={value} value={value}>
{capitalize(value)}
</MultiSelectItem>
))}
</MultiSelect>

<MultiSelect
onValueChange={setSources}
value={sources}
placeholder="Source"
>
{meta?.sources.map((value) => (
<MultiSelectItem key={value} value={value}>
{capitalize(value)}
</MultiSelectItem>
))}
</MultiSelect>
</div>
);
};
85 changes: 55 additions & 30 deletions keep-ui/app/incidents/incident.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,23 @@ import { IncidentPlaceholder } from "./IncidentPlaceholder";
import Modal from "@/components/ui/Modal";
import { PlusCircleIcon } from "@heroicons/react/24/outline";
import PredictedIncidentsTable from "./predicted-incidents-table";
import {SortingState} from "@tanstack/react-table";
import { SortingState } from "@tanstack/react-table";
import { IncidentTableFilters } from "./incident-table-filters";
import { useIncidentFilterContext } from "./incident-table-filters-context";

interface Pagination {
limit: number;
offset: number;
}

interface Filters {
status: string[];
severity: string[];
assignees: string[];
sources: string[];
affected_services: string[];
}

export default function Incident() {
const [incidentsPagination, setIncidentsPagination] = useState<Pagination>({
limit: 20,
Expand All @@ -27,11 +37,28 @@ export default function Incident() {
{ id: "creation_time", desc: true },
]);

const { statuses, severities, assignees, services, sources } =
useIncidentFilterContext();

const filters: Filters = {
status: statuses,
severity: severities,
assignees: assignees,
affected_services: services,
sources: sources,
};

const {
data: incidents,
isLoading,
mutate: mutateIncidents,
} = useIncidents(true, incidentsPagination.limit, incidentsPagination.offset, incidentsSorting[0]);
} = useIncidents(
true,
incidentsPagination.limit,
incidentsPagination.offset,
incidentsSorting[0],
filters
);
const {
data: predictedIncidents,
isLoading: isPredictedLoading,
Expand Down Expand Up @@ -82,27 +109,29 @@ export default function Incident() {
</Card>
) : null}

{isLoading ? (
<Loading />
) : incidents && incidents.items.length > 0 ? (
<div className="h-full flex flex-col">
<div className="flex justify-between items-center">
<div>
<Title>Incidents</Title>
<Subtitle>Manage your incidents</Subtitle>
</div>
<div>
<Button
color="orange"
size="md"
icon={PlusCircleIcon}
onClick={() => setIsFormOpen(true)}
>
Create Incident
</Button>
</div>
<div className="h-full flex flex-col gap-5">
<div className="flex justify-between items-center">
<div>
<Title>Incidents</Title>
<Subtitle>Manage your incidents</Subtitle>
</div>
<Card className="mt-10 flex-grow">

<div>
<Button
color="orange"
size="md"
icon={PlusCircleIcon}
onClick={() => setIsFormOpen(true)}
>
Create Incident
</Button>
</div>
</div>
<IncidentTableFilters />
<Card className="flex-grow">
{isLoading ? (
<Loading />
) : incidents && incidents.items.length > 0 ? (
<IncidentsTable
incidents={incidents}
mutate={mutateIncidents}
Expand All @@ -111,15 +140,11 @@ export default function Incident() {
setSorting={setIncidentsSorting}
editCallback={handleStartEdit}
/>
</Card>
</div>
) : (
<div className="h-full flex">
<Card className="flex-grow flex items-center justify-center">
) : (
<IncidentPlaceholder setIsFormOpen={setIsFormOpen} />
</Card>
</div>
)}
)}
</Card>
</div>
</div>
<Modal
isOpen={isFormOpen}
Expand Down
6 changes: 2 additions & 4 deletions keep-ui/app/incidents/incidents-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,11 @@ export default function IncidentsTable({
id: "services",
header: "Involved Services",
cell: ({ row }) => (
<div className="text-wrap">
<div className="flex flex-wrap gap-1">
{row.original.services
.filter((service) => service !== "null")
.map((service) => (
<Badge key={service} className="mr-1">
{service}
</Badge>
<Badge key={service}>{service}</Badge>
))}
</div>
),
Expand Down
6 changes: 6 additions & 0 deletions keep-ui/app/incidents/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"use client";
import {IncidentFilterContextProvider} from "./incident-table-filters-context";

export default function Layout({ children }: { children: any }) {
return <IncidentFilterContextProvider>{children}</IncidentFilterContextProvider>
}
8 changes: 8 additions & 0 deletions keep-ui/app/incidents/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ export interface PaginatedIncidentAlertsDto {
count: number;
items: AlertDto[];
}

export interface IncidentsMetaDto {
statuses: string[];
severities: string[];
assignees: string[];
services: string[];
sources: string[];
}
4 changes: 4 additions & 0 deletions keep-ui/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ function isValidDate(d: Date) {
return d instanceof Date && !isNaN(d.getTime());
}

export function capitalize(string: string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}

export function toDateObjectWithFallback(date: string | Date) {
/**
* Since we have a weak typing validation in the backend today (lastReceived is just a string),
Expand Down
Loading

0 comments on commit 74bc7d6

Please sign in to comment.