From 8724ed1fdfe7d02df86ee7d50721f6c60277d001 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Fri, 26 Jan 2024 09:00:29 +0200 Subject: [PATCH] feat: separate read and done statuses Signed-off-by: Heikki Hellgren --- .../migrations/20231215_init.js | 1 + .../database/DatabaseNotificationsStore.ts | 22 +- .../src/database/NotificationsStore.ts | 4 + .../src/service/router.ts | 39 +++- plugins/notifications-common/api-report.md | 2 +- plugins/notifications-common/src/types.ts | 2 +- plugins/notifications/api-report.md | 9 +- .../notifications/src/api/NotificationsApi.ts | 4 + .../src/api/NotificationsClient.ts | 16 ++ .../NotificationsPage/NotificationsPage.tsx | 10 +- .../NotificationsTable/NotificationsTable.tsx | 219 ++++++++++-------- 11 files changed, 214 insertions(+), 114 deletions(-) diff --git a/plugins/notifications-backend/migrations/20231215_init.js b/plugins/notifications-backend/migrations/20231215_init.js index e65708a048..08758d06f3 100644 --- a/plugins/notifications-backend/migrations/20231215_init.js +++ b/plugins/notifications-backend/migrations/20231215_init.js @@ -25,6 +25,7 @@ exports.up = async function up(knex) { table.text('image').nullable(); table.datetime('created').notNullable(); table.datetime('read').nullable(); + table.datetime('done').nullable(); table.boolean('saved').defaultTo(false).notNullable(); }); }; diff --git a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts index b0ab25f2f9..533a5b8627 100644 --- a/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts +++ b/plugins/notifications-backend/src/database/DatabaseNotificationsStore.ts @@ -60,12 +60,14 @@ export class DatabaseNotificationsStore implements NotificationsStore { options: NotificationGetOptions | NotificationModifyOptions, ) => { const { user_ref, type } = options; - const query = this.db('notifications').where('userRef', user_ref); + const query = this.db('notifications') + .where('userRef', user_ref) + .orderBy('created', 'desc'); - if (type === 'unread') { - query.whereNull('read'); - } else if (type === 'read') { - query.whereNotNull('read'); + if (type === 'undone') { + query.whereNull('done'); + } else if (type === 'done') { + query.whereNotNull('done'); } else if (type === 'saved') { query.where('saved', true); } @@ -116,6 +118,16 @@ export class DatabaseNotificationsStore implements NotificationsStore { await notificationQuery.update({ read: null }); } + async markDone(options: NotificationModifyOptions): Promise { + const notificationQuery = this.getNotificationsBaseQuery(options); + await notificationQuery.update({ done: new Date(), read: new Date() }); + } + + async markUndone(options: NotificationModifyOptions): Promise { + const notificationQuery = this.getNotificationsBaseQuery(options); + await notificationQuery.update({ done: null, read: null }); + } + async markSaved(options: NotificationModifyOptions): Promise { const notificationQuery = this.getNotificationsBaseQuery(options); await notificationQuery.update({ saved: true }); diff --git a/plugins/notifications-backend/src/database/NotificationsStore.ts b/plugins/notifications-backend/src/database/NotificationsStore.ts index 12397621df..c63de65dc5 100644 --- a/plugins/notifications-backend/src/database/NotificationsStore.ts +++ b/plugins/notifications-backend/src/database/NotificationsStore.ts @@ -43,6 +43,10 @@ export interface NotificationsStore { markUnread(options: NotificationModifyOptions): Promise; + markDone(options: NotificationModifyOptions): Promise; + + markUndone(options: NotificationModifyOptions): Promise; + markSaved(options: NotificationModifyOptions): Promise; markUnsaved(options: NotificationModifyOptions): Promise; diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 3611d407a3..f5299931e0 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -168,10 +168,47 @@ export async function createRouter( router.get('/status', async (req, res) => { const user = await getUser(req); - const status = await store.getStatus({ user_ref: user }); + const status = await store.getStatus({ user_ref: user, type: 'undone' }); res.send(status); }); + router.post('/done', async (req, res) => { + const user = await getUser(req); + const { ids } = req.body; + if (!ids || !Array.isArray(ids)) { + res.status(400).send(); + return; + } + await store.markDone({ user_ref: user, ids }); + + if (signalService) { + await signalService.publish({ + recipients: [user], + message: { action: 'refresh' }, + channel: 'notifications', + }); + } + res.status(200).send({ ids }); + }); + + router.post('/undo', async (req, res) => { + const user = await getUser(req); + const { ids } = req.body; + if (!ids || !Array.isArray(ids)) { + res.status(400).send(); + return; + } + await store.markUndone({ user_ref: user, ids }); + if (signalService) { + await signalService.publish({ + recipients: [user], + message: { action: 'refresh' }, + channel: 'notifications', + }); + } + res.status(200).send({ ids }); + }); + router.post('/read', async (req, res) => { const user = await getUser(req); const { ids } = req.body; diff --git a/plugins/notifications-common/api-report.md b/plugins/notifications-common/api-report.md index 1fb928f3c4..4e34ae2afc 100644 --- a/plugins/notifications-common/api-report.md +++ b/plugins/notifications-common/api-report.md @@ -28,5 +28,5 @@ export type NotificationStatus = { }; // @public (undocumented) -export type NotificationType = 'read' | 'unread' | 'saved'; +export type NotificationType = 'undone' | 'done' | 'saved'; ``` diff --git a/plugins/notifications-common/src/types.ts b/plugins/notifications-common/src/types.ts index 2dd3f9bcb7..c19edb9b24 100644 --- a/plugins/notifications-common/src/types.ts +++ b/plugins/notifications-common/src/types.ts @@ -15,7 +15,7 @@ */ /** @public */ -export type NotificationType = 'read' | 'unread' | 'saved'; +export type NotificationType = 'undone' | 'done' | 'saved'; /** @public */ export type Notification = { diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index b4cb4c6c3e..ebb146a48f 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -31,10 +31,14 @@ export interface NotificationsApi { // (undocumented) getStatus(): Promise; // (undocumented) + markDone(ids: string[]): Promise; + // (undocumented) markRead(ids: string[]): Promise; // (undocumented) markSaved(ids: string[]): Promise; // (undocumented) + markUndone(ids: string[]): Promise; + // (undocumented) markUnread(ids: string[]): Promise; // (undocumented) markUnsaved(ids: string[]): Promise; @@ -53,10 +57,14 @@ export class NotificationsClient implements NotificationsApi { // (undocumented) getStatus(): Promise; // (undocumented) + markDone(ids: string[]): Promise; + // (undocumented) markRead(ids: string[]): Promise; // (undocumented) markSaved(ids: string[]): Promise; // (undocumented) + markUndone(ids: string[]): Promise; + // (undocumented) markUnread(ids: string[]): Promise; // (undocumented) markUnsaved(ids: string[]): Promise; @@ -80,7 +88,6 @@ export const NotificationsSidebarItem: () => React_2.JSX.Element; export const NotificationsTable: (props: { onUpdate: () => void; type: NotificationType; - loading?: boolean; notifications?: Notification_2[]; }) => React_2.JSX.Element; diff --git a/plugins/notifications/src/api/NotificationsApi.ts b/plugins/notifications/src/api/NotificationsApi.ts index 276bc02ded..462787d87f 100644 --- a/plugins/notifications/src/api/NotificationsApi.ts +++ b/plugins/notifications/src/api/NotificationsApi.ts @@ -37,6 +37,10 @@ export interface NotificationsApi { getStatus(): Promise; + markDone(ids: string[]): Promise; + + markUndone(ids: string[]): Promise; + markRead(ids: string[]): Promise; markUnread(ids: string[]): Promise; diff --git a/plugins/notifications/src/api/NotificationsClient.ts b/plugins/notifications/src/api/NotificationsClient.ts index 113aefddca..e3225843ae 100644 --- a/plugins/notifications/src/api/NotificationsClient.ts +++ b/plugins/notifications/src/api/NotificationsClient.ts @@ -52,6 +52,22 @@ export class NotificationsClient implements NotificationsApi { return await this.request('status'); } + async markDone(ids: string[]): Promise { + return await this.request('done', { + method: 'POST', + body: JSON.stringify({ ids: ids }), + headers: { 'Content-Type': 'application/json' }, + }); + } + + async markUndone(ids: string[]): Promise { + return await this.request('undone', { + method: 'POST', + body: JSON.stringify({ ids: ids }), + headers: { 'Content-Type': 'application/json' }, + }); + } + async markRead(ids: string[]): Promise { return await this.request('read', { method: 'POST', diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index 41db91b9cf..9ed74b5c3d 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -37,7 +37,7 @@ const useStyles = makeStyles(_theme => ({ })); export const NotificationsPage = () => { - const [type, setType] = useState('unread'); + const [type, setType] = useState('undone'); const [refresh, setRefresh] = React.useState(false); const { error, value, retry } = useNotificationsApi( @@ -77,16 +77,16 @@ export const NotificationsPage = () => { diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index 3f89e590cf..75d273bfc9 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -20,6 +20,7 @@ import { IconButton, makeStyles, Table, + TableBody, TableCell, TableHead, TableRow, @@ -49,9 +50,13 @@ const useStyles = makeStyles(theme => ({ header: { borderBottom: `1px solid ${theme.palette.divider}`, }, + notificationRow: { cursor: 'pointer', - '&.hideOnHover': { + '&.unread': { + border: '1px solid rgba(255, 255, 255, .3)', + }, + '& .hideOnHover': { display: 'initial', }, '& .showOnHover': { @@ -138,12 +143,12 @@ export const NotificationsTable = (props: { type !== 'saved' && 'Select all'} {selected.length > 0 && `${selected.length} selected`} - {type === 'read' && selected.length > 0 && ( + {type === 'done' && selected.length > 0 && ( )} - {type === 'unread' && selected.length > 0 && ( + {type === 'undone' && selected.length > 0 && (