feat: make notification page filters work
Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi>
This commit is contained in:
@@ -22,14 +22,14 @@ export default async function createPlugin(
|
||||
env: PluginEnvironment,
|
||||
): Promise<Router> {
|
||||
// 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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -21,4 +21,7 @@ export type NotificationStatus = {
|
||||
unread: number;
|
||||
read: number;
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type NotificationType = 'read' | 'unread' | 'saved';
|
||||
```
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @public */
|
||||
export type NotificationType = 'read' | 'unread' | 'saved';
|
||||
|
||||
/** @public */
|
||||
export type Notification = {
|
||||
id: string;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -17,11 +17,13 @@
|
||||
import {
|
||||
Notification,
|
||||
NotificationStatus,
|
||||
NotificationType,
|
||||
} from '@backstage/plugin-notifications-common';
|
||||
|
||||
/** @public */
|
||||
export type NotificationGetOptions = {
|
||||
user_ref: string;
|
||||
type?: NotificationType;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
|
||||
@@ -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<Notification_2[]>;
|
||||
getNotifications(
|
||||
options?: GetNotificationsOptions,
|
||||
): Promise<Notification_2[]>;
|
||||
// (undocumented)
|
||||
getStatus(): Promise<NotificationStatus>;
|
||||
}
|
||||
@@ -30,7 +38,9 @@ export const notificationsApiRef: ApiRef<NotificationsApi>;
|
||||
export class NotificationsClient implements NotificationsApi {
|
||||
constructor(options: { discoveryApi: DiscoveryApi; fetchApi: FetchApi });
|
||||
// (undocumented)
|
||||
getNotifications(): Promise<Notification_2[]>;
|
||||
getNotifications(
|
||||
options?: GetNotificationsOptions,
|
||||
): Promise<Notification_2[]>;
|
||||
// (undocumented)
|
||||
getStatus(): Promise<NotificationStatus>;
|
||||
}
|
||||
|
||||
@@ -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<NotificationsApi>({
|
||||
id: 'plugin.notifications.service',
|
||||
});
|
||||
|
||||
/** @public */
|
||||
export type GetNotificationsOptions = {
|
||||
type?: NotificationType;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
export interface NotificationsApi {
|
||||
getNotifications(): Promise<Notification[]>;
|
||||
getNotifications(options?: GetNotificationsOptions): Promise<Notification[]>;
|
||||
|
||||
getStatus(): Promise<NotificationStatus>;
|
||||
|
||||
// TODO: Mark as read/unread by notification id(s)
|
||||
// TODO: Mark as saved/unsaved by notification id(s)
|
||||
}
|
||||
|
||||
@@ -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<Notification[]> {
|
||||
return await this.get<Notification[]>('notifications');
|
||||
async getNotifications(
|
||||
options?: GetNotificationsOptions,
|
||||
): Promise<Notification[]> {
|
||||
const queryString = new URLSearchParams();
|
||||
if (options?.type) {
|
||||
queryString.append('type', options.type);
|
||||
}
|
||||
|
||||
const urlSegment = `notifications?${queryString}`;
|
||||
|
||||
return await this.get<Notification[]>(urlSegment);
|
||||
}
|
||||
|
||||
async getStatus(): Promise<NotificationStatus> {
|
||||
|
||||
@@ -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<NotificationType>('unread');
|
||||
|
||||
const {
|
||||
loading: _loading,
|
||||
error,
|
||||
value,
|
||||
retry: _retry,
|
||||
} = useNotificationsApi(api => api.getNotifications());
|
||||
} = useNotificationsApi(api => api.getNotifications({ type }), [type]);
|
||||
|
||||
const styles = useStyles();
|
||||
if (error) {
|
||||
return <ErrorPanel error={new Error('Failed to load notifications')} />;
|
||||
}
|
||||
|
||||
// TODO: Make the filter buttons work
|
||||
// TODO: Add signals listener and refresh data on message
|
||||
return (
|
||||
<PageWithHeader title="Notifications" themeId="tool">
|
||||
<Content>
|
||||
<Grid container>
|
||||
<Grid item xs={2}>
|
||||
<Button className={styles.filterButton} startIcon={<Inbox />}>
|
||||
<Button
|
||||
className={styles.filterButton}
|
||||
startIcon={<Inbox />}
|
||||
variant={type === 'unread' ? 'contained' : 'text'}
|
||||
onClick={() => setType('unread')}
|
||||
>
|
||||
Inbox
|
||||
</Button>
|
||||
<Button className={styles.filterButton} startIcon={<Check />}>
|
||||
<Button
|
||||
className={styles.filterButton}
|
||||
startIcon={<Check />}
|
||||
variant={type === 'read' ? 'contained' : 'text'}
|
||||
onClick={() => setType('read')}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
<Button className={styles.filterButton} startIcon={<Bookmark />}>
|
||||
<Button
|
||||
className={styles.filterButton}
|
||||
startIcon={<Bookmark />}
|
||||
variant={type === 'saved' ? 'contained' : 'text'}
|
||||
onClick={() => setType('saved')}
|
||||
>
|
||||
Saved
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
Reference in New Issue
Block a user