From 92c327211adc8ec9076bbb905d6cfe28c12c8b47 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Fri, 15 Dec 2023 14:41:04 +0200 Subject: [PATCH] feat: make notification page filters work Signed-off-by: Heikki Hellgren --- packages/backend/src/plugins/notifications.ts | 16 +++++----- .../src/service/router.ts | 14 +++++++-- plugins/notifications-common/api-report.md | 3 ++ plugins/notifications-common/src/types.ts | 3 ++ plugins/notifications-node/api-report.md | 2 ++ .../database/DatabaseNotificationsStore.ts | 28 +++++++++--------- .../src/database/NotificationsStore.ts | 2 ++ plugins/notifications/api-report.md | 14 +++++++-- .../notifications/src/api/NotificationsApi.ts | 9 +++++- .../src/api/NotificationsClient.ts | 15 ++++++++-- .../NotificationsPage/NotificationsPage.tsx | 29 +++++++++++++++---- 11 files changed, 100 insertions(+), 35 deletions(-) diff --git a/packages/backend/src/plugins/notifications.ts b/packages/backend/src/plugins/notifications.ts index 91c01fb860..bd6976e91b 100644 --- a/packages/backend/src/plugins/notifications.ts +++ b/packages/backend/src/plugins/notifications.ts @@ -22,14 +22,14 @@ export default async function createPlugin( env: PluginEnvironment, ): Promise { // TODO: Remove this test code - // setInterval(() => { - // env.notificationService.send( - // 'user:default/guest', - // 'Test', - // 'This is test notification', - // '/catalog', - // ); - // }, 60000); + setInterval(() => { + env.notificationService.send( + 'user:default/guest', + 'Test', + 'This is test notification', + '/catalog', + ); + }, 60000); return await createRouter({ logger: env.logger, diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 8d9a7472bb..482a987638 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -17,7 +17,10 @@ import { errorHandler } from '@backstage/backend-common'; import express, { Request } from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; -import { NotificationService } from '@backstage/plugin-notifications-node'; +import { + NotificationGetOptions, + NotificationService, +} from '@backstage/plugin-notifications-node'; import { IdentityApi } from '@backstage/plugin-auth-node'; /** @public */ @@ -50,7 +53,14 @@ export async function createRouter( router.get('/notifications', async (req, res) => { const user = await getUser(req); - const notifications = await store.getNotifications({ user_ref: user }); + const opts: NotificationGetOptions = { + user_ref: user, + }; + if (req.query.type) { + opts.type = req.query.type as any; + } + + const notifications = await store.getNotifications(opts); res.send(notifications); }); diff --git a/plugins/notifications-common/api-report.md b/plugins/notifications-common/api-report.md index 6d7d74b64b..f8c5a009bc 100644 --- a/plugins/notifications-common/api-report.md +++ b/plugins/notifications-common/api-report.md @@ -21,4 +21,7 @@ export type NotificationStatus = { unread: number; read: number; }; + +// @public (undocumented) +export type NotificationType = 'read' | 'unread' | 'saved'; ``` diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index c9df78e1ff..d95ad92508 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +/** @public */ +export type NotificationType = 'read' | 'unread' | 'saved'; + /** @public */ export type Notification = { id: string; diff --git a/plugins/notifications-node/api-report.md b/plugins/notifications-node/api-report.md index 68a53cd535..575c6a9b09 100644 --- a/plugins/notifications-node/api-report.md +++ b/plugins/notifications-node/api-report.md @@ -5,6 +5,7 @@ ```ts import { Notification as Notification_2 } from '@backstage/plugin-notifications-common'; import { NotificationStatus } from '@backstage/plugin-notifications-common'; +import { NotificationType } from '@backstage/plugin-notifications-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -32,6 +33,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { // @public (undocumented) export type NotificationGetOptions = { user_ref: string; + type?: NotificationType; }; // @public (undocumented) diff --git a/plugins/notifications-node/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-node/src/database/DatabaseNotificationsStore.ts index 450450d998..63f9685e85 100644 --- a/plugins/notifications-node/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-node/src/database/DatabaseNotificationsStore.ts @@ -55,15 +55,22 @@ export class DatabaseNotificationsStore implements NotificationsStore { return typeof val === 'string' ? Number.parseInt(val, 10) : val ?? 0; }; + private getNotificationsBaseQuery = (options: NotificationGetOptions) => { + const { user_ref, type } = options; + const query = this.db('notifications').where('userRef', user_ref); + + if (type === 'unread') { + query.whereNull('read'); + } else if (type === 'read') { + query.whereNotNull('read'); + } + // TODO: Saved + return query; + }; + async getNotifications(options: NotificationGetOptions) { - const { user_ref } = options; - const notificationQuery = this.db('notifications').where( - 'userRef', - user_ref, - ); - + const notificationQuery = this.getNotificationsBaseQuery(options); const notifications = await notificationQuery.select('*'); - return notifications; } @@ -72,12 +79,7 @@ export class DatabaseNotificationsStore implements NotificationsStore { } async getStatus(options: NotificationGetOptions) { - const { user_ref } = options; - const notificationQuery = this.db('notifications').where( - 'userRef', - user_ref, - ); - + const notificationQuery = this.getNotificationsBaseQuery(options); const unreadQuery = await notificationQuery .clone() .whereNull('read') diff --git a/plugins/notifications-node/src/database/NotificationsStore.ts b/plugins/notifications-node/src/database/NotificationsStore.ts index 349e2cd7fa..b25d32900a 100644 --- a/plugins/notifications-node/src/database/NotificationsStore.ts +++ b/plugins/notifications-node/src/database/NotificationsStore.ts @@ -17,11 +17,13 @@ import { Notification, NotificationStatus, + NotificationType, } from '@backstage/plugin-notifications-common'; /** @public */ export type NotificationGetOptions = { user_ref: string; + type?: NotificationType; }; /** @public */ diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 00ff5f4d4c..9039f688eb 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -12,13 +12,21 @@ 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 { NotificationStatus } from '@backstage/plugin-notifications-common'; +import { NotificationType } from '@backstage/plugin-notifications-common'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; +// @public (undocumented) +export type GetNotificationsOptions = { + type?: NotificationType; +}; + // @public (undocumented) export interface NotificationsApi { // (undocumented) - getNotifications(): Promise; + getNotifications( + options?: GetNotificationsOptions, + ): Promise; // (undocumented) getStatus(): Promise; } @@ -30,7 +38,9 @@ export const notificationsApiRef: ApiRef; export class NotificationsClient implements NotificationsApi { constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi }); // (undocumented) - getNotifications(): Promise; + getNotifications( + options?: GetNotificationsOptions, + ): Promise; // (undocumented) getStatus(): Promise; } diff --git a/plugins/notifications/src/api/NotificationsApi.ts b/plugins/notifications/src/api/NotificationsApi.ts index a4dcf3cd28..aa4b460cb3 100644 --- a/plugins/notifications/src/api/NotificationsApi.ts +++ b/plugins/notifications/src/api/NotificationsApi.ts @@ -17,6 +17,7 @@ import { createApiRef } from '@backstage/core-plugin-api'; import { Notification, NotificationStatus, + NotificationType, } from '@backstage/plugin-notifications-common'; /** @public */ @@ -24,11 +25,17 @@ export const notificationsApiRef = createApiRef({ id: 'plugin.notifications.service', }); +/** @public */ +export type GetNotificationsOptions = { + type?: NotificationType; +}; + /** @public */ export interface NotificationsApi { - getNotifications(): Promise; + getNotifications(options?: GetNotificationsOptions): Promise; getStatus(): Promise; // TODO: Mark as read/unread by notification id(s) + // TODO: Mark as saved/unsaved by notification id(s) } diff --git a/plugins/notifications/src/api/NotificationsClient.ts b/plugins/notifications/src/api/NotificationsClient.ts index 4947900d55..ec6472c37e 100644 --- a/plugins/notifications/src/api/NotificationsClient.ts +++ b/plugins/notifications/src/api/NotificationsClient.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { NotificationsApi } from './NotificationsApi'; +import { GetNotificationsOptions, NotificationsApi } from './NotificationsApi'; import { DiscoveryApi, FetchApi } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { @@ -34,8 +34,17 @@ export class NotificationsClient implements NotificationsApi { this.fetchApi = options.fetchApi; } - async getNotifications(): Promise { - return await this.get('notifications'); + async getNotifications( + options?: GetNotificationsOptions, + ): Promise { + const queryString = new URLSearchParams(); + if (options?.type) { + queryString.append('type', options.type); + } + + const urlSegment = `notifications?${queryString}`; + + return await this.get(urlSegment); } async getStatus(): Promise { diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index e00b45df53..461c39e2aa 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React from 'react'; +import React, { useState } from 'react'; import { Content, ErrorPanel, @@ -32,6 +32,7 @@ import { import Bookmark from '@material-ui/icons/Bookmark'; import Check from '@material-ui/icons/Check'; import Inbox from '@material-ui/icons/Inbox'; +import { NotificationType } from '@backstage/plugin-notifications-common'; const useStyles = makeStyles(_theme => ({ filterButton: { @@ -41,32 +42,48 @@ const useStyles = makeStyles(_theme => ({ })); export const NotificationsPage = () => { + const [type, setType] = useState('unread'); + const { loading: _loading, error, value, retry: _retry, - } = useNotificationsApi(api => api.getNotifications()); + } = useNotificationsApi(api => api.getNotifications({ type }), [type]); const styles = useStyles(); if (error) { return ; } - // TODO: Make the filter buttons work // TODO: Add signals listener and refresh data on message return ( - - -