Skip to content

Commit

Permalink
feat: add Notification List with settings and selenced tabs (#2294)
Browse files Browse the repository at this point in the history
* refactor: remove duplicate SearchLayout

* feat: add Notification List with settings and selenced tabs

Fixes #2286

* refactor: bring in the notification silence add into this PR

* fix: fix issue with link to the notifications

* fix: fix pagination issue not working on the notification pages

* feat: implement all pages for notifications

Fixes #2286

feat: implement the notification silencing fully

Fixes #2286

chore: remove notification rules

fix: pull notifications from send history summary

fix: improve the notifications

fix: fix missing link for notification in some pages

fix: wire through the filters

refactor: use arrays for long select query strings

refactor: move NotificationRule type to types directory

chore: cleanup the tables and fix issue during testing

fix: fix filters not working

fix: fix icon sizes

chore: improve statuses and modal and minor ui issues
  • Loading branch information
mainawycliffe authored Sep 27, 2024
1 parent 074d64c commit 1e060ee
Show file tree
Hide file tree
Showing 43 changed files with 1,527 additions and 587 deletions.
5 changes: 5 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ const config = {
source: "/config/:path*",
destination: "/catalog/:path*",
permanent: true
},
{
source: "/settings/notifications/:path*",
destination: "/notifications/:path*",
permanent: true
}
];
},
Expand Down
75 changes: 47 additions & 28 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React, { ReactNode, useEffect, useState } from "react";
import { IconType } from "react-icons";
import { AiFillHeart } from "react-icons/ai";
import { BsLink, BsToggles } from "react-icons/bs";
import { FaBell, FaTasks } from "react-icons/fa";
import { FaTasks } from "react-icons/fa";
import { HiUser } from "react-icons/hi";
import { ImLifebuoy } from "react-icons/im";
import {
Expand All @@ -27,7 +27,6 @@ import { ErrorBoundary } from "./components/ErrorBoundary";
import EditIntegrationPage from "./components/Integrations/EditIntegrationPage";
import IntegrationsPage from "./components/Integrations/IntegrationsPage";
import JobsHistorySettingsPage from "./components/JobsHistory/JobsHistorySettingsPage";
import NotificationsPage from "./components/Notifications/NotificationsSettingsPage";
import { withAuthorizationAccessCheck } from "./components/Permissions/AuthorizationAccessCheck";
import { SchemaResourcePage } from "./components/SchemaResourcePage";
import { SchemaResource } from "./components/SchemaResourcePage/SchemaResource";
Expand All @@ -54,7 +53,10 @@ 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 NotificationSilencedAddPage from "./pages/Settings/notifications/NotificationSilencedAddPage";
import NotificationsPage from "./pages/Settings/notifications/NotificationsPage";
import NotificationRulesPage from "./pages/Settings/notifications/NotificationsRulesPage";
import NotificationsSilencedPage from "./pages/Settings/notifications/NotificationsSilencedPage";
import { TopologyCardPage } from "./pages/TopologyCard";
import { UsersPage } from "./pages/UsersPage";
import { ConfigInsightsPage } from "./pages/config/ConfigInsightsList";
Expand Down Expand Up @@ -187,13 +189,6 @@ const settingsNav: SettingsNavigationItems = {
featureName: features["settings.job_history"],
resourceName: tables.database
},
{
name: "Notifications",
href: "/settings/notifications",
icon: FaBell,
featureName: features["settings.notifications"],
resourceName: tables.database
},
{
name: "Feature Flags",
href: "/settings/feature-flags",
Expand Down Expand Up @@ -348,54 +343,78 @@ export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) {
</Route>
</Route>

<Route path="settings" element={sidebar}>
<Route path="notifications" element={sidebar}>
<Route
path="connections"
index
element={withAuthorizationAccessCheck(
<ConnectionsPage />,
tables.connections,
<NotificationsPage />,
tables.database,
"read",
true
)}
/>

<Route
path="users"
element={withAuthorizationAccessCheck(
<UsersPage />,
tables.identities,
"read"
)}
/>
<Route
path="jobs"
path="rules"
element={withAuthorizationAccessCheck(
<JobsHistorySettingsPage />,
<NotificationRulesPage />,
tables.database,
"read",
true
)}
/>
<Route path="notifications">

<Route path="silences">
<Route
index
element={withAuthorizationAccessCheck(
<NotificationsPage />,
<NotificationsSilencedPage />,
tables.database,
"read",
true
)}
/>

<Route
path="silence"
path="add"
element={withAuthorizationAccessCheck(
<NotificationSilencePage />,
<NotificationSilencedAddPage />,
tables.database,
"write",
true
)}
/>
</Route>
</Route>

<Route path="settings" element={sidebar}>
<Route
path="connections"
element={withAuthorizationAccessCheck(
<ConnectionsPage />,
tables.connections,
"read",
true
)}
/>
<Route
path="users"
element={withAuthorizationAccessCheck(
<UsersPage />,
tables.identities,
"read"
)}
/>
<Route
path="jobs"
element={withAuthorizationAccessCheck(
<JobsHistorySettingsPage />,
tables.database,
"read",
true
)}
/>

<Route
path="feature-flags"
element={withAuthorizationAccessCheck(
Expand Down
28 changes: 15 additions & 13 deletions src/api/query-hooks/useNotificationsQuery.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { useMutation, useQuery, UseQueryOptions } from "@tanstack/react-query";
import {
NewNotification,
Notification,
UpdateNotification
} from "../../components/Notifications/notificationsTableColumns";
NewNotificationRule,
UpdateNotificationRule
} from "../../components/Notifications/Rules/notificationsRulesTableColumns";
import { toastError, toastSuccess } from "../../components/Toast/toast";
import { useUser } from "../../context";
import { createResource, updateResource } from "../schemaResources";
import {
getNotificationById,
getNotificationsSummary
getNotificationsSummary,
NotificationQueryFilterOptions
} from "../services/notifications";
import { NotificationRules } from "../types/notifications";

export type DatabaseResponse<T extends Record<string, any>> =
| { error: Error; data: null; totalEntries: undefined }
Expand All @@ -21,20 +22,21 @@ export type DatabaseResponse<T extends Record<string, any>> =
};

export function useNotificationsSummaryQuery(
options?: UseQueryOptions<DatabaseResponse<Notification>, Error>
filterOptions: NotificationQueryFilterOptions,
options?: UseQueryOptions<DatabaseResponse<NotificationRules>, Error>
) {
return useQuery<DatabaseResponse<Notification>, Error>(
["notifications", "settings"],
() => getNotificationsSummary(),
return useQuery<DatabaseResponse<NotificationRules>, Error>(
["notifications", "settings", filterOptions],
() => getNotificationsSummary(filterOptions),
options
);
}

export function useGetNotificationsByIDQuery(
id: string,
options?: UseQueryOptions<Notification | undefined, Error>
options?: UseQueryOptions<NotificationRules | undefined, Error>
) {
return useQuery<Notification | undefined, Error>(
return useQuery<NotificationRules | undefined, Error>(
["notifications", "settings", id],
() => getNotificationById(id),
options
Expand All @@ -43,7 +45,7 @@ export function useGetNotificationsByIDQuery(

export const useUpdateNotification = (onSuccess = () => {}) => {
return useMutation(
async (props: Partial<UpdateNotification>) => {
async (props: Partial<UpdateNotificationRule>) => {
const payload = {
...props,
// we want to remove person id, team id and custom services if they are
Expand Down Expand Up @@ -78,7 +80,7 @@ export const useCreateNotification = (onSuccess = () => {}) => {
const { user } = useUser();

return useMutation(
async (data: Partial<NewNotification>) => {
async (data: Partial<NewNotificationRule>) => {
await createResource(
{
api: "config-db",
Expand Down
125 changes: 118 additions & 7 deletions src/api/services/notifications.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,44 @@
import { Notification } from "@flanksource-ui/components/Notifications/notificationsTableColumns";
import { tristateOutputToQueryFilterParam } from "@flanksource-ui/ui/Dropdowns/TristateReactSelect";
import { AVATAR_INFO } from "../../constants";
import { IncidentCommander, NotificationAPI } from "../axios";
import { resolvePostGrestRequestWithPagination } from "../resolve";
import { SilenceNotificationResponse } from "../types/notifications";
import {
NotificationRules,
NotificationSendHistoryApiResponse,
NotificationSilenceItemApiResponse,
SilenceNotificationResponse
} from "../types/notifications";

export function getPagingParams({
pageIndex,
pageSize
}: {
pageIndex?: number;
pageSize?: number;
}) {
const pagingParams =
pageIndex || pageSize
? `&limit=${pageSize}&offset=${pageIndex! * pageSize!}`
: "";
return pagingParams;
}

export const getNotificationsSummary = async ({
pageIndex,
pageSize
}: NotificationQueryFilterOptions) => {
const pagingParams = getPagingParams({ pageIndex, pageSize });

const selectColumns = [
"*",
`person:person_id(${AVATAR_INFO})`,
`team:team_id(id,name,icon)`,
`created_by(${AVATAR_INFO})`
].join(",");

export const getNotificationsSummary = async () => {
return resolvePostGrestRequestWithPagination(
IncidentCommander.get<Notification[] | null>(
`/notifications_summary?select=*,person:person_id(${AVATAR_INFO}),team:team_id(id,name,icon),created_by(${AVATAR_INFO})&order=created_at.desc`,
IncidentCommander.get<NotificationRules[] | null>(
`/notifications_summary?select=${selectColumns}&order=created_at.desc${pagingParams}`,
{
headers: {
Prefer: "count=exact"
Expand All @@ -18,8 +49,14 @@ export const getNotificationsSummary = async () => {
};

export const getNotificationById = async (id: string) => {
const res = await IncidentCommander.get<Notification[] | null>(
`/notifications?id=eq.${id}&select=*,person:person_id(${AVATAR_INFO}),team:team_id(id,name,icon),created_by(${AVATAR_INFO})`
const selectColumns = [
"*",
`person:person_id(${AVATAR_INFO})`,
`team:team_id(id,name,icon)`,
`created_by(${AVATAR_INFO})`
].join(",");
const res = await IncidentCommander.get<NotificationRules[] | null>(
`/notifications?id=eq.${id}&select=${selectColumns}`
);
return res.data ? res.data?.[0] : undefined;
};
Expand All @@ -30,3 +67,77 @@ export const silenceNotification = async (
const res = await NotificationAPI.post("/silence", data);
return res.data;
};

export const getNotificationSendHistory = async ({
pageIndex,
pageSize,
resourceType,
status
}: NotificationQueryFilterOptions & {
status?: string;
resourceType?: string;
}) => {
const pagingParams = getPagingParams({ pageIndex, pageSize });

const resourceTypeParam = resourceType
? tristateOutputToQueryFilterParam(resourceType, "resource_type")
: "";

const statusParam = status
? tristateOutputToQueryFilterParam(status, "status")
: "";

const selectColumns = [
"*"
// `person:person_id(${AVATAR_INFO})`
].join(",");

return resolvePostGrestRequestWithPagination(
IncidentCommander.get<NotificationSendHistoryApiResponse[] | null>(
// currently, this isn't deployed to production
`/notification_send_history_summary?select=${selectColumns}&order=created_at.desc${pagingParams}${resourceTypeParam}${statusParam}`,
{
headers: {
Prefer: "count=exact"
}
}
)
);
};

export type NotificationQueryFilterOptions = {
pageIndex: number;
pageSize: number;
};

export const getNotificationSilences = async ({
pageIndex,
pageSize
}: NotificationQueryFilterOptions) => {
const pagingParams = getPagingParams({ pageIndex, pageSize });

const selectColumns = [
"*",
"checks:check_id(id,name,type,status)",
"catalog:config_id(id,name,type,config_class)",
"component:component_id(id,name,icon)",
`createdBy:created_by(${AVATAR_INFO})`
].join(",");

return resolvePostGrestRequestWithPagination(
IncidentCommander.get<NotificationSilenceItemApiResponse[] | null>(
`/notification_silences?select=${selectColumns}&order=created_at.desc${pagingParams}&deleted_at=is.null`,
{
headers: {
Prefer: "count=exact"
}
}
)
);
};

export const deleteNotificationSilence = async (id: string) => {
return IncidentCommander.patch(`/notification_silences?id=eq.${id}`, {
deleted_at: "now()"
});
};
Loading

0 comments on commit 1e060ee

Please sign in to comment.