From 4f26e9ac3ace0e77f4c1d25613118326aeb492f7 Mon Sep 17 00:00:00 2001 From: Isaac Abadi Date: Sat, 31 Dec 2022 03:38:03 -0500 Subject: [PATCH] Added filters for notifications Added notifications for tasks --- Public API v1.yaml | 2 + backend/notifications.js | 10 +++++ backend/tasks.js | 4 ++ src/api-types/models/NotificationAction.ts | 1 + src/api-types/models/NotificationType.ts | 1 + .../notifications-list.component.ts | 15 ++++--- .../notifications/notifications.component.css | 6 +++ .../notifications.component.html | 11 +++-- .../notifications/notifications.component.ts | 42 ++++++++++++++++++- 9 files changed, 83 insertions(+), 9 deletions(-) diff --git a/Public API v1.yaml b/Public API v1.yaml index 2752977..55d4b3d 100644 --- a/Public API v1.yaml +++ b/Public API v1.yaml @@ -2792,11 +2792,13 @@ components: - play - retry_download - view_download_error + - view_tasks NotificationType: type: string enum: - download_complete - download_error + - task_finished BaseChangePermissionsRequest: required: - permission diff --git a/backend/notifications.js b/backend/notifications.js index daa0a83..a3cfe12 100644 --- a/backend/notifications.js +++ b/backend/notifications.js @@ -1,5 +1,6 @@ const { uuid } = require('uuidv4'); const db_api = require('./db'); +const config_api = require('./config'); exports.sendNotification = async (notification) => { // TODO: hook into third party service @@ -7,6 +8,15 @@ exports.sendNotification = async (notification) => { return notification; } +exports.sendTaskNotification = async (task_obj, confirmed) => { + // workaround for tasks which are user_uid agnostic + const user_uid = config_api.getConfigItem('ytdl_multi_user_mode') ? 'admin' : null; + await db_api.removeAllRecords('notifications', {"data.task_key": task_obj.key}); + const data = {task_key: task_obj.key, task_title: task_obj.title, confirmed: confirmed}; + const notification = exports.createNotification('task_finished', ['view_tasks'], data, user_uid); + return await exports.sendNotification(notification); +} + exports.sendDownloadNotification = async (file, user_uid) => { const data = {file_uid: file.uid, file_title: file.title}; const notification = exports.createNotification('download_complete', ['play'], data, user_uid); diff --git a/backend/tasks.js b/backend/tasks.js index 5fd2fc6..dc91053 100644 --- a/backend/tasks.js +++ b/backend/tasks.js @@ -1,4 +1,5 @@ const db_api = require('./db'); +const notifications_api = require('./notifications'); const youtubedl_api = require('./youtube-dl'); const fs = require('fs-extra'); @@ -128,6 +129,8 @@ exports.executeRun = async (task_key) => { const data = await TASKS[task_key].run(); await db_api.updateRecord('tasks', {key: task_key}, {data: TASKS[task_key]['confirm'] ? data : null, last_ran: Date.now()/1000, running: false}); logger.verbose(`Finished running task ${task_key}`); + const task_obj = await db_api.getRecord('tasks', {key: task_key}); + await notifications_api.sendTaskNotification(task_obj, false); } exports.executeConfirm = async (task_key) => { @@ -141,6 +144,7 @@ exports.executeConfirm = async (task_key) => { await TASKS[task_key].confirm(data); await db_api.updateRecord('tasks', {key: task_key}, {confirming: false, last_confirmed: Date.now()/1000, data: null}); logger.verbose(`Finished confirming task ${task_key}`); + await notifications_api.sendTaskNotification(task_obj, false); } exports.updateTaskSchedule = async (task_key, schedule) => { diff --git a/src/api-types/models/NotificationAction.ts b/src/api-types/models/NotificationAction.ts index 785f7d2..9f97b41 100644 --- a/src/api-types/models/NotificationAction.ts +++ b/src/api-types/models/NotificationAction.ts @@ -6,4 +6,5 @@ export enum NotificationAction { PLAY = 'play', RETRY_DOWNLOAD = 'retry_download', VIEW_DOWNLOAD_ERROR = 'view_download_error', + VIEW_TASKS = 'view_tasks', } \ No newline at end of file diff --git a/src/api-types/models/NotificationType.ts b/src/api-types/models/NotificationType.ts index 6ef911a..26cfee9 100644 --- a/src/api-types/models/NotificationType.ts +++ b/src/api-types/models/NotificationType.ts @@ -5,4 +5,5 @@ export enum NotificationType { DOWNLOAD_COMPLETE = 'download_complete', DOWNLOAD_ERROR = 'download_error', + TASK_FINISHED = 'task_finished', } \ No newline at end of file diff --git a/src/app/components/notifications-list/notifications-list.component.ts b/src/app/components/notifications-list/notifications-list.component.ts index 23a84cf..8b2dd22 100644 --- a/src/app/components/notifications-list/notifications-list.component.ts +++ b/src/app/components/notifications-list/notifications-list.component.ts @@ -15,31 +15,36 @@ export class NotificationsListComponent { NOTIFICATION_PREFIX: { [key in NotificationType]: string } = { download_complete: $localize`Finished downloading`, - download_error: $localize`Download failed` + download_error: $localize`Download failed`, + task_finished: $localize`Task finished` } // Attaches string to the end of the notification text NOTIFICATION_SUFFIX_KEY: { [key in NotificationType]: string } = { download_complete: 'file_title', - download_error: 'download_url' + download_error: 'download_url', + task_finished: 'task_title' } NOTIFICATION_ACTION_TO_STRING: { [key in NotificationAction]: string } = { play: $localize`Play`, retry_download: $localize`Retry download`, - view_download_error: $localize`View error` + view_download_error: $localize`View error`, + view_tasks: $localize`View task` } NOTIFICATION_COLOR: { [key in NotificationAction]: string } = { play: 'primary', retry_download: 'primary', - view_download_error: 'warn' + view_download_error: 'warn', + view_tasks: 'primary' } NOTIFICATION_ICON: { [key in NotificationAction]: string } = { play: 'smart_display', retry_download: 'restart_alt', - view_download_error: 'warning' + view_download_error: 'warning', + view_tasks: 'task' } emitNotificationAction(notification: Notification, action: NotificationAction): void { diff --git a/src/app/components/notifications/notifications.component.css b/src/app/components/notifications/notifications.component.css index 7dce8d9..f9f81b8 100644 --- a/src/app/components/notifications/notifications.component.css +++ b/src/app/components/notifications/notifications.component.css @@ -1,4 +1,10 @@ .notification-title { margin-bottom: 6px; text-align: center +} + +.notifications-list-parent { + max-height: 70vh; + overflow-y: auto; + padding: 0px 10px 10px 10px; } \ No newline at end of file diff --git a/src/app/components/notifications/notifications.component.html b/src/app/components/notifications/notifications.component.html index cd60af8..e13a125 100644 --- a/src/app/components/notifications/notifications.component.html +++ b/src/app/components/notifications/notifications.component.html @@ -1,5 +1,10 @@
No notifications available
-
- - +
+
+ + {{filter.value.label}} + + +
+
diff --git a/src/app/components/notifications/notifications.component.ts b/src/app/components/notifications/notifications.component.ts index 870b421..124bd5a 100644 --- a/src/app/components/notifications/notifications.component.ts +++ b/src/app/components/notifications/notifications.component.ts @@ -1,8 +1,9 @@ import { Component, ElementRef, EventEmitter, OnInit, Output } from '@angular/core'; import { Router } from '@angular/router'; import { PostsService } from 'app/posts.services'; -import { Notification } from 'api-types'; +import { Notification, NotificationType } from 'api-types'; import { NotificationAction } from 'api-types/models/NotificationAction'; +import { MatChipListboxChange } from '@angular/material/chips'; @Component({ selector: 'app-notifications', @@ -12,9 +13,27 @@ import { NotificationAction } from 'api-types/models/NotificationAction'; export class NotificationsComponent implements OnInit { notifications: Notification[] = null; + filtered_notifications: Notification[] = null; @Output() notificationCount = new EventEmitter(); + notificationFilters: { [key in NotificationType]: {key: string, label: string} } = { + download_complete: { + key: 'download_complete', + label: $localize`Download completed` + }, + download_error: { + key: 'download_error', + label: $localize`Download error` + }, + task_finished: { + key: 'task_finished', + label: $localize`Task` + }, + }; + + selectedFilters = []; + constructor(public postsService: PostsService, private router: Router, private elRef: ElementRef) { } ngOnInit(): void { @@ -33,7 +52,10 @@ export class NotificationsComponent implements OnInit { getNotifications(): void { this.postsService.getNotifications().subscribe(res => { this.notifications = res['notifications']; + this.notifications.sort((a, b) => b.timestamp - a.timestamp); this.notificationCount.emit(this.notifications.filter(notification => !notification.read).length); + + this.filterNotifications(); }); } @@ -51,6 +73,9 @@ export class NotificationsComponent implements OnInit { this.deleteNotification(action_info['notification']['uid']); }); break; + case NotificationAction.VIEW_TASKS: + this.router.navigate(['tasks']); + break; default: console.error(`Notification action ${action_info['action']} does not exist!`); break; @@ -60,6 +85,7 @@ export class NotificationsComponent implements OnInit { deleteNotification(uid: string): void { this.postsService.deleteNotification(uid).subscribe(res => { this.notifications.filter(notification => notification['uid'] !== uid); + this.filterNotifications(); this.notificationCount.emit(this.notifications.length); this.getNotifications(); }); @@ -68,6 +94,7 @@ export class NotificationsComponent implements OnInit { deleteAllNotifications(): void { this.postsService.deleteAllNotifications().subscribe(res => { this.notifications = []; + this.filtered_notifications = []; this.getNotifications(); }); this.notificationCount.emit(0); @@ -81,4 +108,17 @@ export class NotificationsComponent implements OnInit { this.notificationCount.emit(0); } + filterNotifications(): void { + this.filtered_notifications = this.notifications.filter(notification => this.selectedFilters.length === 0 || this.selectedFilters.includes(notification.type)); + } + + selectedFiltersChanged(event: MatChipListboxChange): void { + this.selectedFilters = event.value; + this.filterNotifications(); + } + + originalOrder = (): number => { + return 0; + } + }