From 4ceb7aa04cc49d4e5373eb0ce13be258df02b0ca Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 27 Nov 2023 11:58:30 +0100 Subject: [PATCH] Update frontend to use generated openapi calls --- plugins/notifications-common/src/types.ts | 194 +++++++++--------- .../src/api/NotificationsApiImpl.ts | 128 +++--------- .../src/api/notificationsApi.ts | 42 ++-- .../components/NotificationsSidebarItem.tsx | 10 +- .../NotificationsTable/NotificationsTable.tsx | 40 ++-- .../SendNotification/SendNotification.tsx | 9 +- 6 files changed, 174 insertions(+), 249 deletions(-) diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index 689ff26ca37..4b5aa06ace5 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -1,100 +1,100 @@ -export type NotificationAction = { - id: string; // UUID - title: string; - url: string; -}; - -/** - * Basic object representing a notification. - */ -export type Notification = { - id: string; // UUID - created: Date; - readByUser: boolean; - isSystem: boolean; - - origin: string; - title: string; - message?: string; - topic?: string; - - actions: NotificationAction[]; -}; - -/** - * Input data for the POST request (create a notification). - */ -export type CreateNotificationRequest = { - origin: string; - title: string; - message?: string; - actions?: { title: string; url: string }[]; - topic?: string; - targetUsers?: string[]; - targetGroups?: string[]; -}; - -export type NotificationsFilterRequest = { - /** - * Filter notifications whose either title or message contains the provided string. - */ - containsText?: string; - - /** - * Only notifications created after this timestamp will be included. - */ - createdAfter?: Date; - - /** - * See MessageScopes - * Default: DefaultMessageScope - */ - messageScope?: string; - - /** - * The user the query is executed for. Default: DefaultUser - * Its entity must be present in the catalog. - * Conforms IdentityApi.getBackstageIdentity() - */ - user?: string; - /** - * 'false' for user's unread messages, 'true' for read ones. - * If undefined, then both marks. - */ - read?: string; -}; - -/** - * How the result set is sorted. - */ -export type NotificationsSortingRequest = { - fieldName?: string; - direction?: string; -}; - -export type NotificationsOrderByFieldsType = - | 'title' - | 'message' - | 'created' - | 'topic' - | 'origin'; - -export const NotificationsOrderByFields: string[] = [ - 'title', - 'message', - 'created', - 'topic', - 'origin', -]; - -export type NotificationsOrderByDirectionsType = 'asc' | 'desc'; - -export const NotificationsOrderByDirections: string[] = ['asc', 'desc']; - -export type NotificationsQuerySorting = { - fieldName: NotificationsOrderByFieldsType; - direction: NotificationsOrderByDirectionsType; -}; +// export type NotificationAction = { +// id: string; // UUID +// title: string; +// url: string; +// }; + +// /** +// * Basic object representing a notification. +// */ +// export type Notification = { +// id: string; // UUID +// created: Date; +// readByUser: boolean; +// isSystem: boolean; + +// origin: string; +// title: string; +// message?: string; +// topic?: string; + +// actions: NotificationAction[]; +// }; + +// /** +// * Input data for the POST request (create a notification). +// */ +// export type CreateNotificationRequest = { +// origin: string; +// title: string; +// message?: string; +// actions?: { title: string; url: string }[]; +// topic?: string; +// targetUsers?: string[]; +// targetGroups?: string[]; +// }; + +// export type NotificationsFilterRequest = { +// /** +// * Filter notifications whose either title or message contains the provided string. +// */ +// containsText?: string; + +// /** +// * Only notifications created after this timestamp will be included. +// */ +// createdAfter?: Date; + +// /** +// * See MessageScopes +// * Default: DefaultMessageScope +// */ +// messageScope?: string; + +// /** +// * The user the query is executed for. Default: DefaultUser +// * Its entity must be present in the catalog. +// * Conforms IdentityApi.getBackstageIdentity() +// */ +// user?: string; +// /** +// * 'false' for user's unread messages, 'true' for read ones. +// * If undefined, then both marks. +// */ +// read?: string; +// }; + +// /** +// * How the result set is sorted. +// */ +// export type NotificationsSortingRequest = { +// fieldName?: string; +// direction?: string; +// }; + +// export type NotificationsOrderByFieldsType = +// | 'title' +// | 'message' +// | 'created' +// | 'topic' +// | 'origin'; + +// export const NotificationsOrderByFields: string[] = [ +// 'title', +// 'message', +// 'created', +// 'topic', +// 'origin', +// ]; + +// export type NotificationsOrderByDirectionsType = 'asc' | 'desc'; + +// export const NotificationsOrderByDirections: string[] = ['asc', 'desc']; + +// export type NotificationsQuerySorting = { +// fieldName: NotificationsOrderByFieldsType; +// direction: NotificationsOrderByDirectionsType; +// }; /** * MessageScopes diff --git a/plugins/notifications-frontend/src/api/NotificationsApiImpl.ts b/plugins/notifications-frontend/src/api/NotificationsApiImpl.ts index b84f2e000f9..b7108609a5d 100644 --- a/plugins/notifications-frontend/src/api/NotificationsApiImpl.ts +++ b/plugins/notifications-frontend/src/api/NotificationsApiImpl.ts @@ -1,29 +1,31 @@ -import { ConfigApi, IdentityApi } from '@backstage/core-plugin-api'; +import { IdentityApi } from '@backstage/core-plugin-api'; + import { - CreateNotificationRequest, + CreateBody, + DefaultConfig, + GetNotificationsRequest, Notification, -} from '@backstage/plugin-notifications-common'; - + NotificationsApi as NotificationsOpenApi, +} from '../openapi'; import { NotificationMarkAsRead, NotificationsApi, NotificationsCountQuery, - NotificationsFilter, - NotificationsQuery, } from './notificationsApi'; export type NotificationsApiOptions = { - configApi: ConfigApi; identityApi: IdentityApi; }; export class NotificationsApiImpl implements NotificationsApi { - private readonly backendUrl: string; private readonly identityApi: IdentityApi; + private readonly backendRestApi: NotificationsOpenApi; constructor(options: NotificationsApiOptions) { - this.backendUrl = options.configApi.getString('backend.baseUrl'); this.identityApi = options.identityApi; + + const configuration = DefaultConfig; + this.backendRestApi = new NotificationsOpenApi(configuration); } private async getLogedInUsername(): Promise { @@ -34,110 +36,32 @@ export class NotificationsApiImpl implements NotificationsApi { return userEntityRef.slice('start:'.length - 1); } - private addFilter(url: URL, user: string, filter: NotificationsFilter) { - url.searchParams.append('user', user); - - if (filter.containsText) { - url.searchParams.append('containsText', filter.containsText); - } - if (filter.createdAfter) { - url.searchParams.append( - 'createdAfter', - filter.createdAfter.toISOString(), - ); - } - if (filter.messageScope) { - url.searchParams.append('messageScope', filter.messageScope); - } - if (filter.isRead !== undefined) { - url.searchParams.append('read', filter.isRead ? 'true' : 'false'); - } - } - - async post(notification: CreateNotificationRequest): Promise { - const url = new URL(`${this.backendUrl}/api/notifications/notifications`); - - const response = await fetch(url.href, { - method: 'POST', - body: JSON.stringify(notification), - headers: { 'Content-Type': 'application/json' }, + async createNotification(notification: CreateBody): Promise { + const data = await this.backendRestApi.createNotification({ + createBody: notification, }); - const data = await response.json(); - if (response.status !== 200 && response.status !== 201) { - throw new Error(data.message || data.error?.message); - } - - return Promise.resolve(data.messageId); + return data.messageId; } - async getNotifications(query: NotificationsQuery): Promise { - const url = new URL(`${this.backendUrl}/api/notifications/notifications`); + async getNotifications( + query: GetNotificationsRequest, + ): Promise { const user = await this.getLogedInUsername(); - - url.searchParams.append('pageSize', `${query.pageSize}`); - url.searchParams.append('pageNumber', `${query.pageNumber}`); - - if (query.sorting) { - url.searchParams.append('orderBy', `${query.sorting.fieldName}`); - url.searchParams.append('orderByDirec', `${query.sorting.direction}`); - } - - this.addFilter(url, user, query); - - const response = await fetch(url.href); - const data = await response.json(); - if (response.status !== 200 && response.status !== 201) { - throw new Error(data.message); - } - - if (!Array.isArray(data)) { - throw new Error('Unexpected format of notifications received'); - } - - return data; + return this.backendRestApi.getNotifications({ ...query, user }); } async getNotificationsCount(query: NotificationsCountQuery): Promise { - const url = new URL( - `${this.backendUrl}/api/notifications/notifications/count`, - ); const user = await this.getLogedInUsername(); - - this.addFilter(url, user, query); - - const response = await fetch(url.href); - const data = await response.json(); - if (response.status !== 200 && response.status !== 201) { - throw new Error(data.message); - } - - const count = parseInt(data.count, 10); - if (Number.isNaN(count)) { - throw new Error('Unexpected format of notifications count received'); - } - - return count; + const data = await this.backendRestApi.getNotificationsCount({ + ...query, + user, + }); + return data.count; } - async markAsRead({ - notificationId, - isRead, - }: NotificationMarkAsRead): Promise { - const url = new URL( - `${this.backendUrl}/api/notifications/notifications/read`, - ); + async markAsRead(params: NotificationMarkAsRead): Promise { const user = await this.getLogedInUsername(); - url.searchParams.append('read', isRead ? 'true' : 'false'); - url.searchParams.append('user', user); - url.searchParams.append('messageId', notificationId); - - const response = await fetch(url.href, { - method: 'PUT', - }); - - if (response.status !== 200 && response.status !== 201) { - throw new Error('Failed to mark the message as read'); - } + return this.backendRestApi.setRead({ ...params, user }); } } diff --git a/plugins/notifications-frontend/src/api/notificationsApi.ts b/plugins/notifications-frontend/src/api/notificationsApi.ts index c2f9c5be8d5..3d4e5c2ba6e 100644 --- a/plugins/notifications-frontend/src/api/notificationsApi.ts +++ b/plugins/notifications-frontend/src/api/notificationsApi.ts @@ -1,32 +1,26 @@ import { createApiRef } from '@backstage/core-plugin-api'; + import { - CreateNotificationRequest, + CreateBody, + GetNotificationsCountRequest, + GetNotificationsRequest, Notification, - NotificationsQuerySorting, -} from '@backstage/plugin-notifications-common'; - -export type NotificationsFilter = { - containsText?: string; - createdAfter?: Date; - messageScope?: 'all' | 'user' | 'system'; - isRead?: boolean; // if undefined, include both read and unread -}; - -export type NotificationsQuery = NotificationsFilter & { - pageSize: number; - pageNumber: number; - - sorting?: NotificationsQuerySorting; -}; -export type NotificationsCountQuery = NotificationsFilter; - -export type NotificationMarkAsRead = { - notificationId: string; - isRead: boolean; -}; + SetReadRequest, +} from '../openapi'; + +export type NotificationsCreateRequest = CreateBody; + +export type NotificationsQuery = Omit; + +export type NotificationsCountQuery = Omit< + GetNotificationsCountRequest, + 'user' +>; + +export type NotificationMarkAsRead = Omit; export interface NotificationsApi { /** Create a notification. Returns its new ID. */ - post(notification: CreateNotificationRequest): Promise; + createNotification(notification: NotificationsCreateRequest): Promise; /** Read a list of notifications based on filter parameters. */ getNotifications(query?: NotificationsQuery): Promise; diff --git a/plugins/notifications-frontend/src/components/NotificationsSidebarItem.tsx b/plugins/notifications-frontend/src/components/NotificationsSidebarItem.tsx index 5931aa2f044..1c0fc5d38b2 100644 --- a/plugins/notifications-frontend/src/components/NotificationsSidebarItem.tsx +++ b/plugins/notifications-frontend/src/components/NotificationsSidebarItem.tsx @@ -2,7 +2,6 @@ import React from 'react'; import { SidebarItem } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { Notification } from '@backstage/plugin-notifications-common'; import { IconButton, @@ -17,6 +16,7 @@ import NotificationsOffIcon from '@material-ui/icons/NotificationsOff'; import { notificationsApiRef } from '../api'; import { NOTIFICATIONS_ROUTE } from '../constants'; +import { Notification } from '../openapi'; import { usePollingEffect } from './usePollingEffect'; const NotificationsErrorIcon = () => ( @@ -58,7 +58,7 @@ export const NotificationsSidebarItem = ({ try { setUnreadCount( await notificationsApi.getNotificationsCount({ - isRead: false, + read: false, messageScope: 'user', }), ); @@ -67,10 +67,8 @@ export const NotificationsSidebarItem = ({ pageSize: 1, pageNumber: 1, createdAfter: pageLoadingTime, - sorting: { - fieldName: 'created', - direction: 'desc', - }, + orderBy: 'created', + orderByDirec: 'desc', messageScope: 'system', }); diff --git a/plugins/notifications-frontend/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications-frontend/src/components/NotificationsTable/NotificationsTable.tsx index f38c9fd4884..58d7fe674c8 100644 --- a/plugins/notifications-frontend/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications-frontend/src/components/NotificationsTable/NotificationsTable.tsx @@ -8,10 +8,6 @@ import { TableColumn, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { - Notification, - NotificationsQuerySorting, -} from '@backstage/plugin-notifications-common'; import { MaterialTableProps } from '@material-table/core'; import { Grid, IconButton, Tooltip } from '@material-ui/core'; @@ -19,8 +15,14 @@ import { makeStyles } from '@material-ui/core/styles'; import MarkAsReadIcon from '@material-ui/icons/CheckCircle'; import debounce from 'lodash/debounce'; -import { notificationsApiRef, NotificationsFilter } from '../../api'; +import { notificationsApiRef, NotificationsQuery } from '../../api'; import { DebounceDelayMs } from '../../constants'; +import { + GetNotificationsCountMessageScopeEnum, + GetNotificationsOrderByDirecEnum, + GetNotificationsOrderByEnum, + Notification, +} from '../../openapi'; import MarkAsUnreadIcon from './MarkAsUnreadIcon'; import { CreatedAfterOptions, @@ -38,7 +40,7 @@ const useStyles = makeStyles({ }); export type NotificationsTableProps = { - messageScope: NonNullable; + messageScope: GetNotificationsCountMessageScopeEnum; }; export const NotificationsTable = ({ @@ -52,7 +54,11 @@ export const NotificationsTable = ({ const [createdAfter, setCreatedAfter] = React.useState('lastWeek'); const [unreadOnly, setUnreadOnly] = React.useState(true); const [sorting, setSorting] = React.useState< - NotificationsQuerySorting | undefined + | { + orderBy: GetNotificationsOrderByEnum; + orderByDirec: GetNotificationsOrderByDirecEnum; + } + | undefined >(); const [reload, setReload] = React.useState(0); @@ -60,8 +66,8 @@ export const NotificationsTable = ({ (notification: Notification) => { notificationsApi .markAsRead({ - notificationId: notification.id, - isRead: !notification.readByUser, + messageId: notification.id, + read: !notification.readByUser, }) .then(() => setReload(Date.now())); }, @@ -79,22 +85,26 @@ export const NotificationsTable = ({ }> => { const createdAfterDate = CreatedAfterOptions[createdAfter].getDate(); - const commonParams: NotificationsFilter = { + const commonParams: Pick< + NotificationsQuery, + 'containsText' | 'createdAfter' | 'messageScope' | 'read' + > = { containsText, createdAfter: createdAfterDate, messageScope, }; if (unreadOnly !== undefined) { - commonParams.isRead = !unreadOnly; + commonParams.read = !unreadOnly; } const data = await notificationsApi.getNotifications({ ...commonParams, + ...sorting, pageSize, pageNumber: pageNumber + 1 /* BE starts at 1 */, - sorting, }); + // TODO: extend BE to get both in a single query/response const total = await notificationsApi.getNotificationsCount({ ...commonParams, @@ -169,13 +179,13 @@ export const NotificationsTable = ({ const onOrderChange = React.useCallback< NonNullable['onOrderChange']> - >((orderBy, direction) => { + >((orderBy, orderByDirec) => { if (orderBy < 0) { setSorting(undefined); return; } - const fieldNames: NotificationsQuerySorting['fieldName'][] = [ + const fieldNames: GetNotificationsOrderByEnum[] = [ /* Keep the order in sync with the column definitions bellow */ 'title', 'message', @@ -185,7 +195,7 @@ export const NotificationsTable = ({ ]; const fieldName = fieldNames[orderBy]; - setSorting({ fieldName, direction }); + setSorting({ orderBy: fieldName, orderByDirec }); }, []); const columns = React.useMemo( diff --git a/plugins/notifications-frontend/src/components/SendNotification/SendNotification.tsx b/plugins/notifications-frontend/src/components/SendNotification/SendNotification.tsx index c5f3c4e4c16..a851c79c30b 100644 --- a/plugins/notifications-frontend/src/components/SendNotification/SendNotification.tsx +++ b/plugins/notifications-frontend/src/components/SendNotification/SendNotification.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; -import { CreateNotificationRequest } from '@backstage/plugin-notifications-common'; import { makeStyles } from '@material-ui/core'; import Alert from '@mui/material/Alert'; @@ -10,7 +9,7 @@ import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; -import { notificationsApiRef } from '../../api'; +import { notificationsApiRef, NotificationsCreateRequest } from '../../api'; const useStyles = makeStyles({ container: { @@ -39,7 +38,7 @@ export const SendNotification = () => { try { const parsedActions = actions ? JSON.parse(actions) : undefined; - const notification: CreateNotificationRequest = { + const notification: NotificationsCreateRequest = { origin, title, message, @@ -49,7 +48,7 @@ export const SendNotification = () => { targetGroups, }; - const id = await notificationsApi.post(notification); + const id = await notificationsApi.createNotification(notification); setNotificationId(id); } catch (_e) { const e = _e as Error; @@ -58,7 +57,7 @@ export const SendNotification = () => { }; const getCurl = () => { - const data: CreateNotificationRequest = { + const data: NotificationsCreateRequest = { title, origin, };