Skip to content

Commit

Permalink
feat: add Notification List with settings and selenced tabs
Browse files Browse the repository at this point in the history
Fixes #2286
  • Loading branch information
mainawycliffe committed Sep 17, 2024
1 parent 1ade4f7 commit 469dec6
Show file tree
Hide file tree
Showing 7 changed files with 290 additions and 44 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
52 changes: 43 additions & 9 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,9 @@ 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 NotificationsPage from "./components/Notifications/NotificationsPage";
import NotificationRulesPage from "./components/Notifications/NotificationsRulesPage";
import NotificationsSilencedPage from "./components/Notifications/NotificationsSilencedPage";
import { withAuthorizationAccessCheck } from "./components/Permissions/AuthorizationAccessCheck";
import { SchemaResourcePage } from "./components/SchemaResourcePage";
import { SchemaResource } from "./components/SchemaResourcePage/SchemaResource";
Expand Down Expand Up @@ -186,13 +188,13 @@ 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: "Notifications",
// href: "/settings/notifications",
// icon: FaBell,
// featureName: features["settings.notifications"],
// resourceName: tables.database
// },
{
name: "Feature Flags",
href: "/settings/feature-flags",
Expand Down Expand Up @@ -347,6 +349,38 @@ export function IncidentManagerRoutes({ sidebar }: { sidebar: ReactNode }) {
</Route>
</Route>

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

<Route
path="rules"
element={withAuthorizationAccessCheck(
<NotificationRulesPage />,
tables.database,
"read",
true
)}
/>

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

<Route path="settings" element={sidebar}>
<Route
path="connections"
Expand Down
9 changes: 9 additions & 0 deletions src/components/Layout/SearchLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { RefreshButton } from "@flanksource-ui/ui/Buttons/RefreshButton";
import { HelpDropdown } from "@flanksource-ui/ui/MenuBar/HelpDropdown";
import { FaBell } from "react-icons/fa";
import { Link } from "react-router-dom";
import DashboardErrorBoundary from "../../components/Errors/DashboardErrorBoundary";
import { UserProfileDropdown } from "../../components/Users/UserProfile";

Expand Down Expand Up @@ -36,6 +38,13 @@ export function SearchLayout({
{onRefresh && (
<RefreshButton onClick={onRefresh} animate={loading} />
)}
<Link
to={{
pathname: "/notifications"
}}
>
<FaBell className="cursor-pointer text-gray-500" size={20} />
</Link>
<HelpDropdown />
<UserProfileDropdown />
</div>
Expand Down
112 changes: 112 additions & 0 deletions src/components/Notifications/NotificationTabsLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { tables } from "@flanksource-ui/context/UserAccessContext/permissions";
import {
BreadcrumbChild,
BreadcrumbNav,
BreadcrumbRoot
} from "@flanksource-ui/ui/BreadcrumbNav";
import { Head } from "@flanksource-ui/ui/Head";
import { refreshButtonClickedTrigger } from "@flanksource-ui/ui/SlidingSideBar/SlidingSideBar";
import TabbedLinks from "@flanksource-ui/ui/Tabs/TabbedLinks";
import clsx from "clsx";
import { useAtom } from "jotai";
import { AiFillPlusCircle } from "react-icons/ai";
import ConfigSidebar from "../Configs/Sidebar/ConfigSidebar";
import { ErrorBoundary } from "../ErrorBoundary";
import { SearchLayout } from "../Layout/SearchLayout";
import { AuthorizationAccessCheck } from "../Permissions/AuthorizationAccessCheck";

const tabLinks = [
{ label: "Notifications", path: "/notifications" },
{ label: "Rules", path: "/notifications/rules" },
{ label: "Silenced Notifications", path: "/notifications/silenced" }
];

type NotificationTabsLinksProps = {
activeTab: "Notifications" | "Rules" | "Silenced Notifications";
children: React.ReactNode;
refresh: () => void;
isLoading: boolean;
className?: string;
setIsModalOpen?: (isOpen: boolean) => void;
};

export default function NotificationTabsLinks({
activeTab,
children,
refresh = () => {},
isLoading,
className,
setIsModalOpen = () => {}
}: NotificationTabsLinksProps) {
const pageTitle = activeTab;

const [, setRefreshButtonClickedTrigger] = useAtom(
refreshButtonClickedTrigger
);

return (
<>
<Head prefix={pageTitle} />
<SearchLayout
title={
<BreadcrumbNav
list={[
<BreadcrumbRoot key="notifications" link="/settings/jobs">
Notifications
</BreadcrumbRoot>,
...(activeTab === "Rules"
? [
<BreadcrumbChild key="rules">Rules</BreadcrumbChild>,

<AuthorizationAccessCheck
resource={tables.notifications}
action="write"
key="add-notifications"
>
<button
key="notifications-add"
type="button"
className=""
onClick={() => setIsModalOpen(true)}
>
<AiFillPlusCircle size={32} className="text-blue-600" />
</button>
</AuthorizationAccessCheck>
]
: []),
...(activeTab === "Silenced Notifications"
? [
<BreadcrumbChild key="silenced-notifications">
Silenced
</BreadcrumbChild>
]
: [])
]}
/>
}
onRefresh={() => {
setRefreshButtonClickedTrigger((prev) => prev + 1);
refresh();
}}
loading={isLoading}
contentClass="p-0 h-full overflow-y-hidden"
>
<div className={`flex h-full flex-row`}>
<div className="flex flex-1 flex-col">
<TabbedLinks
activeTabName={activeTab}
tabLinks={tabLinks}
contentClassName={clsx(
"bg-white border border-t-0 border-gray-300 flex-1 overflow-y-auto",
className
)}
>
<ErrorBoundary>{children}</ErrorBoundary>
</TabbedLinks>
</div>
<ConfigSidebar />
</div>
</SearchLayout>
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { tables } from "@flanksource-ui/context/UserAccessContext/permissions";
import { useState } from "react";
import { AiFillPlusCircle } from "react-icons/ai";
import {
useCreateNotification,
useNotificationsSummaryQuery
} from "../../api/query-hooks/useNotificationsQuery";
import { BreadcrumbNav, BreadcrumbRoot } from "../../ui/BreadcrumbNav";
import { Head } from "../../ui/Head";
import { SearchLayout } from "../../ui/Layout/SearchLayout";
import { Modal } from "../../ui/Modal";
import { AuthorizationAccessCheck } from "../Permissions/AuthorizationAccessCheck";
import NotificationsForm from "./NotificationsForm";
import NotificationsTable from "./NotificationsTable";
import { Notification } from "./notificationsTableColumns";
import NotificationTabsLinks from "./NotificationTabsLinks";

export default function NotificationsPage() {
const [isOpen, setIsOpen] = useState(false);
Expand All @@ -35,34 +30,11 @@ export default function NotificationsPage() {

return (
<>
<Head prefix="Notifications" />
<SearchLayout
title={
<BreadcrumbNav
list={[
<BreadcrumbRoot key="notifications" link="/settings/jobs">
Notifications
</BreadcrumbRoot>,
<AuthorizationAccessCheck
resource={tables.notifications}
action="write"
key="add-notifications"
>
<button
key="notifications-add"
type="button"
className=""
onClick={() => setIsOpen(true)}
>
<AiFillPlusCircle size={32} className="text-blue-600" />
</button>
</AuthorizationAccessCheck>
]}
/>
}
onRefresh={refetch}
contentClass="p-0 h-full"
loading={isLoading || isRefetching}
<NotificationTabsLinks
activeTab={"Notifications"}
refresh={refetch}
isLoading={false}
setIsModalOpen={setIsOpen}
>
<div className="flex h-full w-full flex-1 flex-col p-6 pb-0">
<NotificationsTable
Expand All @@ -71,7 +43,7 @@ export default function NotificationsPage() {
refresh={refetch}
/>
</div>
</SearchLayout>
</NotificationTabsLinks>
<Modal
open={isOpen}
onClose={() => setIsOpen(false)}
Expand Down
57 changes: 57 additions & 0 deletions src/components/Notifications/NotificationsRulesPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useState } from "react";
import {
useCreateNotification,
useNotificationsSummaryQuery
} from "../../api/query-hooks/useNotificationsQuery";
import { Modal } from "../../ui/Modal";
import NotificationsForm from "./NotificationsForm";
import NotificationsTable from "./NotificationsTable";
import { Notification } from "./notificationsTableColumns";
import NotificationTabsLinks from "./NotificationTabsLinks";

export default function NotificationRulesPage() {
const [isOpen, setIsOpen] = useState(false);

const { data, isLoading, refetch, isRefetching } =
useNotificationsSummaryQuery({
keepPreviousData: true
});

const { mutate: createNotification } = useCreateNotification(() => {
refetch();
setIsOpen(false);
});

const onSubmit = (notification: Partial<Notification>) => {
createNotification(notification);
};

const notifications = data?.data;

return (
<>
<NotificationTabsLinks
activeTab={"Rules"}
refresh={refetch}
isLoading={false}
setIsModalOpen={setIsOpen}
>
<div className="flex h-full w-full flex-1 flex-col p-6 pb-0">
<NotificationsTable
notifications={notifications ?? []}
isLoading={isLoading || isRefetching}
refresh={refetch}
/>
</div>
</NotificationTabsLinks>
<Modal
open={isOpen}
onClose={() => setIsOpen(false)}
title="Create Notification"
bodyClass="flex flex-col w-full flex-1 h-full overflow-y-auto"
>
<NotificationsForm onSubmit={onSubmit} onDeleted={refetch} />
</Modal>
</>
);
}
Loading

0 comments on commit 469dec6

Please sign in to comment.