feat(notifications): use pagination on the backend layer

The NotificationsPage uses pagination by the backend to avoid large
datasets to be loaded into frontend.

Signed-off-by: Marek Libra <marek.libra@gmail.com>
This commit is contained in:
Marek Libra
2024-02-26 11:48:46 +01:00
parent 19d3cb9d12
commit 07abfe16cb
9 changed files with 155 additions and 20 deletions
@@ -40,9 +40,17 @@ export type UpdateNotificationsOptions = {
saved?: boolean;
};
/** @public */
export type GetNotificationsResponse = {
notifications: Notification[];
totalCount: number;
};
/** @public */
export interface NotificationsApi {
getNotifications(options?: GetNotificationsOptions): Promise<Notification[]>;
getNotifications(
options?: GetNotificationsOptions,
): Promise<GetNotificationsResponse>;
getNotification(id: string): Promise<Notification>;
@@ -15,6 +15,7 @@
*/
import {
GetNotificationsOptions,
GetNotificationsResponse,
NotificationsApi,
UpdateNotificationsOptions,
} from './NotificationsApi';
@@ -40,7 +41,7 @@ export class NotificationsClient implements NotificationsApi {
async getNotifications(
options?: GetNotificationsOptions,
): Promise<Notification[]> {
): Promise<GetNotificationsResponse> {
const queryString = new URLSearchParams();
if (options?.limit !== undefined) {
queryString.append('limit', options.limit.toString(10));
@@ -59,7 +60,7 @@ export class NotificationsClient implements NotificationsApi {
}
const urlSegment = `?${queryString}`;
return await this.request<Notification[]>(urlSegment);
return await this.request<GetNotificationsResponse>(urlSegment);
}
async getNotification(id: string): Promise<Notification> {
@@ -35,13 +35,18 @@ export const NotificationsPage = () => {
const [refresh, setRefresh] = React.useState(false);
const { lastSignal } = useSignal('notifications');
const [unreadOnly, setUnreadOnly] = React.useState<boolean | undefined>(true);
const [pageNumber, setPageNumber] = React.useState(0);
const [pageSize, setPageSize] = React.useState(5);
const [containsText, setContainsText] = React.useState<string>();
const [createdAfter, setCreatedAfter] = React.useState<string>('lastWeek');
const { error, value, retry, loading } = useNotificationsApi(
// TODO: add pagination and other filters
api => {
const options: GetNotificationsOptions = { search: containsText };
const options: GetNotificationsOptions = {
search: containsText,
limit: pageSize,
offset: pageNumber * pageSize,
};
if (unreadOnly !== undefined) {
options.read = !unreadOnly;
}
@@ -53,7 +58,7 @@ export const NotificationsPage = () => {
return api.getNotifications(options);
},
[containsText, unreadOnly, createdAfter],
[containsText, unreadOnly, createdAfter, pageNumber, pageSize],
);
useEffect(() => {
@@ -94,9 +99,14 @@ export const NotificationsPage = () => {
<Grid item xs={10}>
<NotificationsTable
isLoading={loading}
notifications={value}
notifications={value?.notifications}
onUpdate={onUpdate}
setContainsText={setContainsText}
onPageChange={setPageNumber}
onRowsPerPageChange={setPageSize}
page={pageNumber}
pageSize={pageSize}
totalCount={value?.totalCount}
/>
</Grid>
</Grid>
@@ -15,25 +15,34 @@
*/
import React, { useMemo } from 'react';
import throttle from 'lodash/throttle';
// @ts-ignore
import RelativeTime from 'react-relative-time';
import { Box, IconButton, Tooltip, Typography } from '@material-ui/core';
import { Notification } from '@backstage/plugin-notifications-common';
import { notificationsApiRef } from '../../api';
import { useApi } from '@backstage/core-plugin-api';
import MarkAsUnreadIcon from '@material-ui/icons/Markunread';
import MarkAsReadIcon from '@material-ui/icons/CheckCircle';
// @ts-ignore
import RelativeTime from 'react-relative-time';
import { Link, Table, TableColumn } from '@backstage/core-components';
import {
Link,
Table,
TableProps,
TableColumn,
} from '@backstage/core-components';
const ThrottleDelayMs = 1000;
/** @public */
export type NotificationsTableProps = {
export type NotificationsTableProps = Pick<
TableProps,
'onPageChange' | 'onRowsPerPageChange' | 'page' | 'totalCount'
> & {
isLoading?: boolean;
notifications?: Notification[];
onUpdate: () => void;
setContainsText: (search: string) => void;
pageSize: number;
};
/** @public */
@@ -42,6 +51,11 @@ export const NotificationsTable = ({
notifications = [],
onUpdate,
setContainsText,
onPageChange,
onRowsPerPageChange,
page,
pageSize,
totalCount,
}: NotificationsTableProps) => {
const notificationsApi = useApi(notificationsApiRef);
@@ -156,16 +170,15 @@ export const NotificationsTable = ({
isLoading={isLoading}
options={{
search: true,
// TODO: add pagination
// paging: true,
// pageSize,
paging: true,
pageSize,
header: false,
sorting: false,
}}
// onPageChange={setPageNumber}
// onRowsPerPageChange={setPageSize}
// page={offset}
// totalCount={value?.totalCount}
onPageChange={onPageChange}
onRowsPerPageChange={onRowsPerPageChange}
page={page}
totalCount={totalCount}
onSearchChange={throttledContainsTextHandler}
data={notifications}
columns={compactColumns}