feat(notifications): implement severity of notifications

Signed-off-by: Marek Libra <marek.libra@gmail.com>
This commit is contained in:
Marek Libra
2024-03-03 09:15:37 +01:00
parent 739415b07c
commit dff7a7e9e2
13 changed files with 249 additions and 16 deletions
+2
View File
@@ -11,6 +11,7 @@ import { DiscoveryApi } from '@backstage/core-plugin-api';
import { FetchApi } from '@backstage/core-plugin-api';
import { JSX as JSX_2 } from 'react';
import { Notification as Notification_2 } from '@backstage/plugin-notifications-common';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
import { NotificationStatus } from '@backstage/plugin-notifications-common';
import { default as React_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
@@ -26,6 +27,7 @@ export type GetNotificationsOptions = {
createdAfter?: Date;
sort?: 'created' | 'topic' | 'origin';
sortOrder?: 'asc' | 'desc';
minimalSeverity?: NotificationSeverity;
};
// @public (undocumented)
@@ -16,6 +16,7 @@
import { createApiRef } from '@backstage/core-plugin-api';
import {
Notification,
NotificationSeverity,
NotificationStatus,
} from '@backstage/plugin-notifications-common';
@@ -34,6 +35,7 @@ export type GetNotificationsOptions = {
createdAfter?: Date;
sort?: 'created' | 'topic' | 'origin';
sortOrder?: 'asc' | 'desc';
minimalSeverity?: NotificationSeverity;
};
/** @public */
@@ -67,6 +67,9 @@ export class NotificationsClient implements NotificationsApi {
if (options?.createdAfter !== undefined) {
queryString.append('created_after', options.createdAfter.toISOString());
}
if (options?.minimalSeverity !== undefined) {
queryString.append('minimal_severity', options.minimalSeverity);
}
const urlSegment = `?${queryString}`;
return await this.request<GetNotificationsResponse>(urlSegment);
@@ -25,6 +25,7 @@ import {
Typography,
} from '@material-ui/core';
import { GetNotificationsOptions } from '../../api';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
export type SortBy = Required<
Pick<GetNotificationsOptions, 'sort' | 'sortOrder'>
@@ -39,6 +40,8 @@ export type NotificationsFiltersProps = {
onSortingChanged: (sortBy: SortBy) => void;
saved?: boolean;
onSavedChanged: (checked: boolean | undefined) => void;
severity: NotificationSeverity;
onSeverityChanged: (severity: NotificationSeverity) => void;
};
export const CreatedAfterOptions: {
@@ -108,6 +111,13 @@ const getSortByText = (sortBy?: SortBy): string => {
return 'newest';
};
const AllSeverityOptions: { [key in NotificationSeverity]: string } = {
critical: 'Critical',
high: 'High',
normal: 'Normal',
low: 'Low',
};
export const NotificationsFilters = ({
sorting,
onSortingChanged,
@@ -117,6 +127,8 @@ export const NotificationsFilters = ({
onCreatedAfterChanged,
saved,
onSavedChanged,
severity,
onSeverityChanged,
}: NotificationsFiltersProps) => {
const sortByText = getSortByText(sorting);
@@ -162,6 +174,14 @@ export const NotificationsFilters = ({
viewValue = 'read';
}
const handleOnSeverityChanged = (
event: React.ChangeEvent<{ name?: string; value: unknown }>,
) => {
const value: NotificationSeverity =
(event.target.value as NotificationSeverity) || 'normal';
onSeverityChanged(value);
};
return (
<>
<Grid container>
@@ -169,6 +189,7 @@ export const NotificationsFilters = ({
<Typography variant="h6">Filters</Typography>
<Divider variant="fullWidth" />
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-view">View</InputLabel>
@@ -185,14 +206,16 @@ export const NotificationsFilters = ({
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-view">
<InputLabel id="notifications-filter-created">
Created after
</InputLabel>
<Select
label="Created after"
labelId="notifications-filter-created"
placeholder="Notifications since"
value={createdAfter}
onChange={handleOnCreatedAfterChanged}
@@ -212,6 +235,7 @@ export const NotificationsFilters = ({
<Select
label="Sort by"
labelId="notifications-filter-sort"
placeholder="Field to sort by"
value={sortByText}
onChange={handleOnSortByChanged}
@@ -224,6 +248,27 @@ export const NotificationsFilters = ({
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>
<FormControl fullWidth variant="outlined" size="small">
<InputLabel id="notifications-filter-severity">
Minimal severity
</InputLabel>
<Select
label="Minimal severity"
labelId="notifications-filter-severity"
value={severity}
onChange={handleOnSeverityChanged}
>
{Object.keys(AllSeverityOptions).map((key: string) => (
<MenuItem value={key} key={key}>
{AllSeverityOptions[key as NotificationSeverity]}
</MenuItem>
))}
</Select>
</FormControl>
</Grid>
</Grid>
</>
);
@@ -32,6 +32,7 @@ import {
SortByOptions,
} from '../NotificationsFilters';
import { GetNotificationsOptions } from '../../api';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
export const NotificationsPage = () => {
const [refresh, setRefresh] = React.useState(false);
@@ -45,6 +46,8 @@ export const NotificationsPage = () => {
const [sorting, setSorting] = React.useState<SortBy>(
SortByOptions.newest.sortBy,
);
const [severity, setSeverity] =
React.useState<NotificationSeverity>('normal');
const { error, value, retry, loading } = useNotificationsApi(
api => {
@@ -52,6 +55,7 @@ export const NotificationsPage = () => {
search: containsText,
limit: pageSize,
offset: pageNumber * pageSize,
minimalSeverity: severity,
...(sorting || {}),
};
if (unreadOnly !== undefined) {
@@ -76,6 +80,7 @@ export const NotificationsPage = () => {
pageSize,
sorting,
saved,
severity,
],
);
@@ -114,6 +119,8 @@ export const NotificationsPage = () => {
sorting={sorting}
saved={saved}
onSavedChanged={setSaved}
severity={severity}
onSeverityChanged={setSeverity}
/>
</Grid>
<Grid item xs={10}>
@@ -33,6 +33,7 @@ import MarkAsUnreadIcon from '@material-ui/icons/Markunread' /* TODO: use Drafts
import MarkAsReadIcon from '@material-ui/icons/CheckCircle';
import MarkAsUnsavedIcon from '@material-ui/icons/LabelOff' /* TODO: use BookmarkRemove and BookmarkAdd once we have mui 5 icons */;
import MarkAsSavedIcon from '@material-ui/icons/Label';
import { SeverityIcon } from './SeverityIcon';
const ThrottleDelayMs = 1000;
@@ -93,6 +94,12 @@ export const NotificationsTable = ({
const compactColumns = React.useMemo(
(): TableColumn<Notification>[] => [
{
width: '1rem',
render: (notification: Notification) => (
<SeverityIcon severity={notification.payload?.severity} />
),
},
{
customFilterAndSearch: () =>
true /* Keep it on backend due to pagination. If recent flickering is an issue, implement search here as well. */,
@@ -0,0 +1,39 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { NotificationSeverity } from '@backstage/plugin-notifications-common';
import NormalIcon from '@material-ui/icons/CheckOutlined';
import CriticalIcon from '@material-ui/icons/ErrorOutline';
import HighIcon from '@material-ui/icons/WarningOutlined';
import LowIcon from '@material-ui/icons/InfoOutlined';
export const SeverityIcon = ({
severity,
}: {
severity?: NotificationSeverity;
}) => {
switch (severity) {
case 'critical':
return <CriticalIcon htmlColor="#C9190B" />;
case 'high':
return <HighIcon htmlColor="#F0AB00" />;
case 'low':
return <LowIcon htmlColor="#2B9AF3" />;
case 'normal':
default:
return <NormalIcon htmlColor="#5BA352" />;
}
};