From 90ff980a00bed2deda859580fb3972a1fed8fc48 Mon Sep 17 00:00:00 2001 From: mdatelle Date: Thu, 12 Sep 2024 12:39:12 -0400 Subject: [PATCH] refactor: update notifications.resolver to handle filtering - Updates the getNotifications function to use the refactored getNotificationsFromPaths function - Adds filtering logic to the updated getNotificationsFromPaths function - Update JSdocs --- .gitignore | 3 + api/.gitignore | 82 +++++++++++++++++++ api/src/store/modules/notifications.ts | 1 - .../notifications/notifications.resolver.ts | 6 +- .../notifications/notifications.service.ts | 71 ++++++++-------- 5 files changed, 123 insertions(+), 40 deletions(-) create mode 100644 api/.gitignore diff --git a/.gitignore b/.gitignore index b442b9d6c..af416ada4 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,9 @@ typings/ # OSX .DS_Store +# Jetbrains Settings Files +.idea + # Temp dir for tests test/__temp__/* diff --git a/api/.gitignore b/api/.gitignore new file mode 100644 index 000000000..50aadee2f --- /dev/null +++ b/api/.gitignore @@ -0,0 +1,82 @@ +# Logs +./logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +coverage-ts + +# nyc test coverage +.nyc_output + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# Visual Studio Code workspace +.vscode/* +!.vscode/extensions.json + +# OSX +.DS_Store + +# Temp dir for tests +test/__temp__/* + +# Built files +dist + +# Typescript +typescript + +# Ultra runner +.ultra.cache.json + +# Github actions +RELEASE_NOTES.md + +# Docker Deploy Folder +deploy/* +!deploy/.gitkeep + +# pkg cache +.pkg-cache + +# IDE Settings Files +.idea diff --git a/api/src/store/modules/notifications.ts b/api/src/store/modules/notifications.ts index c39589dad..c676c0484 100644 --- a/api/src/store/modules/notifications.ts +++ b/api/src/store/modules/notifications.ts @@ -4,7 +4,6 @@ import { Importance, NotificationType, type Notification, - type NotificationInput, } from '@app/graphql/generated/api/types'; import { NotificationSchema } from '@app/graphql/generated/api/operations'; import { type RootState, type AppDispatch } from '@app/store/index'; diff --git a/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts b/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts index 553a7f92b..ac4a94a88 100644 --- a/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts @@ -1,7 +1,5 @@ import { type NotificationFilter } from '@app/graphql/generated/api/types'; -import { getters } from '@app/store/index'; import { Query, Resolver, Args, Subscription } from '@nestjs/graphql'; -import { GraphQLError } from 'graphql'; import { UseRoles } from 'nest-access-control'; import { PUBSUB_CHANNEL, createSubscription } from '@app/core/pubsub'; import { NotificationsService } from './notifications.service'; @@ -18,9 +16,9 @@ export class NotificationsResolver { }) public async notifications( @Args('filter') - { limit, importance, type, offset }: NotificationFilter + filters: NotificationFilter ) { - return await this.notificationsService.getNotifications(); + await this.notificationsService.getNotifications(filters); } @Subscription('notificationAdded') diff --git a/api/src/unraid-api/graph/resolvers/notifications/notifications.service.ts b/api/src/unraid-api/graph/resolvers/notifications/notifications.service.ts index 98d243960..863db3850 100644 --- a/api/src/unraid-api/graph/resolvers/notifications/notifications.service.ts +++ b/api/src/unraid-api/graph/resolvers/notifications/notifications.service.ts @@ -1,10 +1,10 @@ import { NotificationIni } from '@app/core/types/states/notification'; import { parseConfig } from '@app/core/utils/misc/parse-config'; import { NotificationSchema } from '@app/graphql/generated/api/operations'; -import { Importance, NotificationType, type Notification, } from '@app/graphql/generated/api/types'; +import {Importance, NotificationType, type Notification, NotificationFilter,} from '@app/graphql/generated/api/types'; import { getters } from '@app/store'; import { Injectable } from '@nestjs/common'; -import { readdir, stat } from 'fs/promises'; +import { readdir } from 'fs/promises'; import { join } from 'path'; import { Logger } from '@nestjs/common'; import { isFulfilled, isRejected } from '@app/utils'; @@ -18,58 +18,59 @@ export class NotificationsService { * * @returns An array of all notifications in the system. */ - public async getNotifications(): Promise { + public async getNotifications(filters: NotificationFilter): Promise { this.logger.debug('Getting Notifications'); - const logErrors = (error: unknown) => this.logger.error(error); const notificationBasePath = getters.dynamix().notify!.path; + const unreadDirectoryPath = join(notificationBasePath, NotificationType.UNREAD.toLowerCase()); + const archivedDirectoryPath = join(notificationBasePath, NotificationType.ARCHIVE.toLowerCase()); + const directoryPath = filters.type === NotificationType.ARCHIVE ? archivedDirectoryPath : unreadDirectoryPath; + const unreadFiles = await this.getFilesInFolder(directoryPath); + const [notifications] = await this.getNotificationsFromPaths(unreadFiles, filters); - const [unreadNotifications, unreadNotificationErrors] = await this.getUnreadNotifications(notificationBasePath); - - this.logger.debug(`${unreadNotifications.length} Unread Notifications`); - this.logger.debug(`${unreadNotificationErrors.length} Unread Notification Errors`); - unreadNotificationErrors.forEach(logErrors); - - const [archivedNotifications, archivedNotificationErrors] = await this.getArchiveNotifications(notificationBasePath); - - this.logger.debug(`${archivedNotifications.length} Archived Notifications`); - this.logger.debug(`${archivedNotificationErrors.length} Archived Notification Errors`); - archivedNotificationErrors.forEach(logErrors); - - return [...unreadNotifications, ...archivedNotifications]; + return notifications; } - private getUnreadNotifications(notificationsDirectory: string) { - const unreadsDirectoryPath = join(notificationsDirectory, NotificationType.UNREAD.toLowerCase()); - return this.getNotificationsFromDirectory(unreadsDirectoryPath, NotificationType.UNREAD); - } + private async getFilesInFolder(folderPath: string): Promise { + const contents = await readdir(folderPath); - private getArchiveNotifications(notificationsDirectory: string) { - const archivedDirectoryPath = join(notificationsDirectory, NotificationType.ARCHIVE.toLowerCase()); - return this.getNotificationsFromDirectory(archivedDirectoryPath, NotificationType.ARCHIVE); + return contents.map((content) => join(folderPath, content)); } /** - * Given a directory path, reads all the files in the directory, + * Given a an array of files, reads and filters all the files in the directory, * and attempts to parse each file as a Notification. * Returns an array of two elements: - * - the first element is an array of successfully parsed Notifications, + * - the first element is an array of successfully parsed and filtered Notifications, * - the second element is an array of errors for any files that failed parsing. - * @param containingDirectory the directory to read - * @param type the type of notification to load + * @param files the files to read + * @param filters the filters to apply to the notifications * @returns an array of two elements: [successes, errors/failures] */ - private async getNotificationsFromDirectory(containingDirectory: string, type: NotificationType): Promise<[Notification[], unknown[]]> { - const loadNotification = (filePath: string) => this.loadNotificationFile(filePath, type); + private async getNotificationsFromPaths(files: string[], filters: NotificationFilter): Promise<[Notification[], unknown[]]> { + const { limit, importance, type, offset } = filters; - const contents = await readdir(containingDirectory); - const absolutePaths = contents.map((content) => join(containingDirectory, content)); - const fileReads = absolutePaths.map(loadNotification); + const fileReads = files.slice(offset, limit + offset).map((file) => this.loadNotificationFile(file, type ?? NotificationType.UNREAD)); const results = await Promise.allSettled(fileReads); return [ - results.filter(isFulfilled).map((result) => result.value), - results.filter(isRejected).map((result) => result.reason), + results.filter(isFulfilled).map(result => result.value).filter((notification) => { + if (importance && importance !== notification.importance) { + return false; + } + + if (type && type !== notification.type) { + return false; + } + + return true; + }) + .sort( + (a, b) => + new Date(b.timestamp ?? 0).getTime() - + new Date(a.timestamp ?? 0).getTime() + ), + results.filter(isRejected).map((result) => result.reason), ]; }