Skip to content

Commit

Permalink
feat: add silence notification form page
Browse files Browse the repository at this point in the history
Fixes #2284
  • Loading branch information
mainawycliffe committed Sep 18, 2024
1 parent 65b232c commit 201e6f1
Show file tree
Hide file tree
Showing 14 changed files with 556 additions and 68 deletions.
30 changes: 22 additions & 8 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
import { ConnectionsPage } from "./pages/Settings/ConnectionsPage";
import { EventQueueStatusPage } from "./pages/Settings/EventQueueStatus";
import { FeatureFlagsPage } from "./pages/Settings/FeatureFlagsPage";
import NotificationSilencePage from "./pages/Settings/NotificationSilencePage";
import { TopologyCardPage } from "./pages/TopologyCard";
import { UsersPage } from "./pages/UsersPage";
import { ConfigInsightsPage } from "./pages/config/ConfigInsightsList";
Expand Down Expand Up @@ -374,14 +375,27 @@ export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) {
true
)}
/>
<Route
path="notifications"
element={withAuthorizationAccessCheck(
<NotificationsPage />,
tables.database,
"read"
)}
/>
<Route path="notifications">
<Route
index
element={withAuthorizationAccessCheck(
<NotificationsPage />,
tables.database,
"read",
true
)}
/>

<Route
path="silence"
element={withAuthorizationAccessCheck(
<NotificationSilencePage />,
tables.database,
"write",
true
)}
/>
</Route>
<Route
path="feature-flags"
element={withAuthorizationAccessCheck(
Expand Down
9 changes: 9 additions & 0 deletions src/api/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ export const Rback = axios.create({
}
});

export const NotificationAPI = axios.create({
baseURL: `${API_BASE}/notification`,
headers: {
Accept: "application/json",
Prefer: "return=representation",
"Content-Type": "application/json"
}
});

for (const client of [
Auth,
IncidentCommander,
Expand Down
12 changes: 10 additions & 2 deletions src/api/services/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Notification } from "../../components/Notifications/notificationsTableColumns";
import { Notification } from "@flanksource-ui/components/Notifications/notificationsTableColumns";
import { AVATAR_INFO } from "../../constants";
import { IncidentCommander } from "../axios";
import { IncidentCommander, NotificationAPI } from "../axios";
import { resolvePostGrestRequestWithPagination } from "../resolve";
import { SilenceNotificationResponse } from "../types/notifications";

export const getNotificationsSummary = async () => {
return resolvePostGrestRequestWithPagination(
Expand All @@ -22,3 +23,10 @@ export const getNotificationById = async (id: string) => {
);
return res.data ? res.data?.[0] : undefined;
};

export const silenceNotification = async (
data: SilenceNotificationResponse
) => {
const res = await NotificationAPI.post("/silence", data);
return res.data;
};
10 changes: 10 additions & 0 deletions src/api/services/topology.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,13 @@ export const getCheckNames = async () => {
const res = await IncidentCommander.get<HealthCheckNames[]>(`/check_names`);
return res.data;
};

export const getCanaryNames = async () => {
const res = await IncidentCommander.get<
{
id: string;
name: string;
}[]
>(`/canary_names`);
return res.data;
};
11 changes: 11 additions & 0 deletions src/api/types/notifications.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export type SilenceNotificationResponse = {
id: string;
component_id: string;
config_id: string;
check_id: string;
canary_id: string;
from: string;
until: string;
description: string;
recursive: boolean;
};
47 changes: 47 additions & 0 deletions src/components/Forms/Formik/FormikCanaryDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { getCanaryNames } from "@flanksource-ui/api/services/topology";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import FormikSelectDropdown from "./FormikSelectDropdown";

type FormikCanaryDropdownProps = {
name: string;
label?: string;
required?: boolean;
hint?: string;

className?: string;
};

export default function FormikCanaryDropdown({
name,
label,
required = false,
hint,
className = "flex flex-col space-y-2 py-2"
}: FormikCanaryDropdownProps) {
const { isLoading, data: canary } = useQuery({
queryKey: ["canaries", "canary_names"],
queryFn: () => getCanaryNames()
});

const options = useMemo(
() =>
canary?.map((canary) => ({
label: canary.name,
value: canary.id
})),
[canary]
);

return (
<FormikSelectDropdown
name={name}
className={className}
options={options}
label={label}
isLoading={isLoading}
required={required}
hint={hint}
/>
);
}
79 changes: 79 additions & 0 deletions src/components/Forms/Formik/FormikDurationPicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
rangeOptionsCategories,
TimeRangeOption
} from "@flanksource-ui/ui/TimeRangePicker/rangeOptions";
import { TimeRangePicker } from "@flanksource-ui/ui/TimeRangePicker/TimeRangePicker";
import dayjs from "dayjs";
import { useFormikContext } from "formik";
import { useMemo } from "react";

type FormikDurationPickerProps = {
fieldNames: {
from: string;
to: string;
};
label: string;
className?: string;
placeholder?: string;
};

export default function FormikDurationPicker({
fieldNames: { from, to },
label,
placeholder = "Select duration",
className = "flex flex-col py-2"
}: FormikDurationPickerProps) {
const { values, setFieldValue } =
useFormikContext<Record<string, string | undefined>>();

const value = useMemo(() => {
// if until is a valid date, then, set time range value to and from
if (dayjs(values[to]).isValid()) {
return {
display: "Custom",
from: values[from] ?? "",
to: values[to] ?? "",
type: "absolute"
} satisfies TimeRangeOption;
}

const relativeValues = rangeOptionsCategories.find(
(category) =>
category.name === "Relative time ranges" && category.type === "future"
)?.options;

return {
type: "relative",
display: values[to]
? relativeValues?.find(
(v) => v.type === "relative" && v.range === values[to]
)?.display!
: "",
range: values[to] ?? ""
} satisfies TimeRangeOption;
}, [from, to, values]);

return (
<div className={className}>
{label && <label className={`form-label`}>{label}</label>}
<div className="flex w-full flex-col">
<TimeRangePicker
value={value}
placeholder={placeholder}
onChange={(value) => {
console.log(value, "value");
if (value.type === "absolute") {
setFieldValue(from, value.from);
setFieldValue(to, value.to);
} else if (value.type === "relative") {
setFieldValue(to, value.range);
setFieldValue(from, "now");
}
}}
showFutureTimeRanges
className="w-full"
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import FormikResourceSelectorDropdown from "@flanksource-ui/components/Forms/Formik/FormikResourceSelectorDropdown";
import { Switch } from "@flanksource-ui/ui/FormControls/Switch";
import { useFormikContext } from "formik";
import { useMemo, useState } from "react";

export default function FormikNotificationResourceField() {
const { values } = useFormikContext<Record<string, any>>();

const component_id = values.component_id;
const config_id = values.config_id;
const check_id = values.check_id;
const canary_id = values.canary_id;

const [switchOption, setSwitchOption] = useState<
"Component" | "Catalog" | "Check" | "Canary"
>(() => {
if (component_id) {
return "Component";
}
if (config_id) {
return "Catalog";
}
if (check_id) {
return "Check";
}
if (canary_id) {
return "Canary";
}
return "Catalog";
});

const fieldName = useMemo(() => {
switch (switchOption) {
case "Component":
return {
name: "component_id",
label: "Component"
};
case "Catalog":
return {
name: "config_id",
label: "Catalog"
};
case "Check":
return {
name: "check_id",
label: "Check"
};
case "Canary":
return {
name: "canary_id",
label: "Canary"
};
}
}, [switchOption]);

return (
<div className="flex flex-col gap-2">
<label className={`form-label`}>Resource</label>
<div>
<div className="flex w-full flex-row">
<Switch
options={["Catalog", "Component", "Check"]}
className="w-auto"
itemsClassName=""
defaultValue="Go Template"
value={switchOption}
onChange={(v) => {
setSwitchOption(v);
// clear the other fields if the user selects a different option
if (v === "Component") {
values.config_id = null;
values.check_id = null;
values.canary_id = null;
}

if (v === "Catalog") {
values.component_id = null;
values.check_id = null;
values.canary_id = null;
}

if (v === "Check") {
values.component_id = null;
values.config_id = null;
values.canary_id = null;
}

if (v === "Canary") {
values.component_id = null;
values.config_id = null;
values.check_id = null;
}
}}
/>
</div>

<FormikResourceSelectorDropdown
required
name={fieldName.name}
checkResourceSelector={switchOption === "Check" ? [{}] : undefined}
componentResourceSelector={
switchOption === "Component" ? [{}] : undefined
}
configResourceSelector={switchOption === "Catalog" ? [{}] : undefined}
/>
</div>
</div>
);
}
Loading

0 comments on commit 201e6f1

Please sign in to comment.