Skip to content

Commit

Permalink
Update frontend to use generated openapi calls
Browse files Browse the repository at this point in the history
  • Loading branch information
mareklibra committed Nov 27, 2023
1 parent a503afa commit c0114ec
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 152 deletions.
128 changes: 26 additions & 102 deletions plugins/notifications-frontend/src/api/NotificationsApiImpl.ts
Original file line number Diff line number Diff line change
@@ -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<string> {
Expand All @@ -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<string> {
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<string> {
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<Notification[]> {
const url = new URL(`${this.backendUrl}/api/notifications/notifications`);
async getNotifications(
query: GetNotificationsRequest,
): Promise<Notification[]> {
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<number> {
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<void> {
const url = new URL(
`${this.backendUrl}/api/notifications/notifications/read`,
);
async markAsRead(params: NotificationMarkAsRead): Promise<void> {
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 });
}
}
42 changes: 18 additions & 24 deletions plugins/notifications-frontend/src/api/notificationsApi.ts
Original file line number Diff line number Diff line change
@@ -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<GetNotificationsRequest, 'user'>;

export type NotificationsCountQuery = Omit<
GetNotificationsCountRequest,
'user'
>;

export type NotificationMarkAsRead = Omit<SetReadRequest, 'user'>;
export interface NotificationsApi {
/** Create a notification. Returns its new ID. */
post(notification: CreateNotificationRequest): Promise<string>;
createNotification(notification: NotificationsCreateRequest): Promise<string>;

/** Read a list of notifications based on filter parameters. */
getNotifications(query?: NotificationsQuery): Promise<Notification[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = () => (
Expand Down Expand Up @@ -58,7 +58,7 @@ export const NotificationsSidebarItem = ({
try {
setUnreadCount(
await notificationsApi.getNotificationsCount({
isRead: false,
read: false,
messageScope: 'user',
}),
);
Expand All @@ -67,10 +67,8 @@ export const NotificationsSidebarItem = ({
pageSize: 1,
pageNumber: 1,
createdAfter: pageLoadingTime,
sorting: {
fieldName: 'created',
direction: 'desc',
},
orderBy: 'created',
orderByDirec: 'desc',
messageScope: 'system',
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@ 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';
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,
Expand All @@ -38,7 +40,7 @@ const useStyles = makeStyles({
});

export type NotificationsTableProps = {
messageScope: NonNullable<NotificationsFilter['messageScope']>;
messageScope: GetNotificationsCountMessageScopeEnum;
};

export const NotificationsTable = ({
Expand All @@ -52,16 +54,20 @@ export const NotificationsTable = ({
const [createdAfter, setCreatedAfter] = React.useState<string>('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);

const onMarkAsReadSwitch = React.useCallback(
(notification: Notification) => {
notificationsApi
.markAsRead({
notificationId: notification.id,
isRead: !notification.readByUser,
messageId: notification.id,
read: !notification.readByUser,
})
.then(() => setReload(Date.now()));
},
Expand All @@ -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,
Expand Down Expand Up @@ -169,13 +179,13 @@ export const NotificationsTable = ({

const onOrderChange = React.useCallback<
NonNullable<MaterialTableProps<Notification>['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',
Expand All @@ -185,7 +195,7 @@ export const NotificationsTable = ({
];
const fieldName = fieldNames[orderBy];

setSorting({ fieldName, direction });
setSorting({ orderBy: fieldName, orderByDirec });
}, []);

const columns = React.useMemo(
Expand Down
Loading

0 comments on commit c0114ec

Please sign in to comment.