diff --git a/api/src/graphql/generated/api/types.ts b/api/src/graphql/generated/api/types.ts index 252817c2f..4e84baa1a 100644 --- a/api/src/graphql/generated/api/types.ts +++ b/api/src/graphql/generated/api/types.ts @@ -632,6 +632,7 @@ export type Mutation = { connectSignIn: Scalars['Boolean']['output']; connectSignOut: Scalars['Boolean']['output']; createNotification: Notification; + deleteAllNotifications: NotificationOverview; deleteNotification: NotificationOverview; /** Delete a user */ deleteUser?: Maybe; @@ -1723,7 +1724,7 @@ export type DirectiveResolverFn> = ResolversObject<{ +export type ResolversInterfaceTypes<_RefType extends Record> = ResolversObject<{ Node: ( ArrayType ) | ( Config ) | ( Connect ) | ( Docker ) | ( Info ) | ( Network ) | ( Notification ) | ( Notifications ) | ( Service ) | ( Vars ); UserAccount: ( Me ) | ( User ); }>; @@ -2355,6 +2356,7 @@ export type MutationResolvers>; connectSignOut?: Resolver; createNotification?: Resolver>; + deleteAllNotifications?: Resolver; deleteNotification?: Resolver>; deleteUser?: Resolver, ParentType, ContextType, RequireFields>; enableDynamicRemoteAccess?: Resolver>; diff --git a/api/src/graphql/generated/client/fragment-masking.ts b/api/src/graphql/generated/client/fragment-masking.ts index 3347e2dbc..04b9e1ad0 100644 --- a/api/src/graphql/generated/client/fragment-masking.ts +++ b/api/src/graphql/generated/client/fragment-masking.ts @@ -20,25 +20,45 @@ export function useFragment( _documentNode: DocumentTypeDecoration, fragmentType: FragmentType> ): TType; +// return nullable if `fragmentType` is undefined +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | undefined +): TType | undefined; // return nullable if `fragmentType` is nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: FragmentType> | null +): TType | null; +// return nullable if `fragmentType` is nullable or undefined export function useFragment( _documentNode: DocumentTypeDecoration, fragmentType: FragmentType> | null | undefined ): TType | null | undefined; // return array of non-nullable if `fragmentType` is array of non-nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> +): Array; +// return array of nullable if `fragmentType` is array of nullable +export function useFragment( + _documentNode: DocumentTypeDecoration, + fragmentType: Array>> | null | undefined +): Array | null | undefined; +// return readonly array of non-nullable if `fragmentType` is array of non-nullable export function useFragment( _documentNode: DocumentTypeDecoration, fragmentType: ReadonlyArray>> ): ReadonlyArray; -// return array of nullable if `fragmentType` is array of nullable +// return readonly array of nullable if `fragmentType` is array of nullable export function useFragment( _documentNode: DocumentTypeDecoration, fragmentType: ReadonlyArray>> | null | undefined ): ReadonlyArray | null | undefined; export function useFragment( _documentNode: DocumentTypeDecoration, - fragmentType: FragmentType> | ReadonlyArray>> | null | undefined -): TType | ReadonlyArray | null | undefined { + fragmentType: FragmentType> | Array>> | ReadonlyArray>> | null | undefined +): TType | Array | ReadonlyArray | null | undefined { return fragmentType as any; } diff --git a/api/src/graphql/generated/client/gql.ts b/api/src/graphql/generated/client/gql.ts index 7ff96c0d5..c01d48e70 100644 --- a/api/src/graphql/generated/client/gql.ts +++ b/api/src/graphql/generated/client/gql.ts @@ -11,6 +11,7 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document- * 3. It does not support dead code elimination, so it will add unused operations. * * Therefore it is highly recommended to use the babel or swc plugin for production. + * Learn more about it here: https://the-guild.dev/graphql/codegen/plugins/presets/preset-client#reducing-bundle-size */ const documents = { "\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n": types.sendRemoteGraphQLResponseDocument, diff --git a/api/src/graphql/schema/types/notifications/notifications.graphql b/api/src/graphql/schema/types/notifications/notifications.graphql index 1381e5dcd..16523f028 100644 --- a/api/src/graphql/schema/types/notifications/notifications.graphql +++ b/api/src/graphql/schema/types/notifications/notifications.graphql @@ -17,6 +17,7 @@ type Query { type Mutation { createNotification(input: NotificationData!): Notification! deleteNotification(id: String!, type: NotificationType!): NotificationOverview! + deleteAllNotifications: NotificationOverview! """ Marks a notification as archived. """ 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 a2d43a439..3dfd97732 100644 --- a/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts @@ -77,6 +77,11 @@ export class NotificationsResolver { return overview; } + @Mutation() + public async deleteAllNotifications(): Promise { + return this.notificationsService.deleteAllNotifications(); + } + @Mutation() public archiveNotification(@Args('id') id: string) { return this.notificationsService.archiveNotification({ id }); diff --git a/web/components/Notifications/Sidebar.vue b/web/components/Notifications/Sidebar.vue index 14e94cbd6..8c50fcb93 100644 --- a/web/components/Notifications/Sidebar.vue +++ b/web/components/Notifications/Sidebar.vue @@ -3,9 +3,10 @@ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/co import { useMutation } from '@vue/apollo-composable'; // eslint-disable-next-line @typescript-eslint/consistent-type-imports -- false positive :( import { Importance, NotificationType } from '~/composables/gql/graphql'; -import { archiveAllNotifications } from './graphql/notification.query'; +import { archiveAllNotifications, deleteAllNotifications } from './graphql/notification.query'; const { mutate: archiveAll, loading: loadingArchiveAll } = useMutation(archiveAllNotifications); +const { mutate: deleteAll, loading: loadingDeleteAll } = useMutation(deleteAllNotifications); const { teleportTarget, determineTeleportTarget } = useTeleport(); const importance = ref(undefined); @@ -51,9 +52,11 @@ const importance = ref(undefined); diff --git a/web/components/Notifications/graphql/notification.query.ts b/web/components/Notifications/graphql/notification.query.ts index 317242b88..3b8ed325f 100644 --- a/web/components/Notifications/graphql/notification.query.ts +++ b/web/components/Notifications/graphql/notification.query.ts @@ -67,6 +67,19 @@ export const deleteNotification = graphql(/* GraphQL */ ` } `); +export const deleteAllNotifications = graphql(/* GraphQL */ ` + mutation DeleteAllNotifications { + deleteAllNotifications { + archive { + total + } + unread { + total + } + } + } +`); + export const unreadOverview = graphql(/* GraphQL */ ` query Overview { notifications { diff --git a/web/composables/gql/gql.ts b/web/composables/gql/gql.ts index e062be4d6..2180fa42c 100644 --- a/web/composables/gql/gql.ts +++ b/web/composables/gql/gql.ts @@ -20,6 +20,7 @@ const documents = { "\n mutation ArchiveNotification($id: String!) {\n archiveNotification(id: $id) {\n ...NotificationFragment\n }\n }\n": types.ArchiveNotificationDocument, "\n mutation ArchiveAllNotifications {\n archiveAll {\n unread {\n total\n }\n archive {\n info\n warning\n alert\n total\n }\n }\n }\n": types.ArchiveAllNotificationsDocument, "\n mutation DeleteNotification($id: String!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n": types.DeleteNotificationDocument, + "\n mutation DeleteAllNotifications {\n deleteAllNotifications {\n archive {\n total\n }\n unread {\n total\n }\n }\n }\n": types.DeleteAllNotificationsDocument, "\n query Overview {\n notifications {\n overview {\n unread {\n info\n warning\n alert\n total\n }\n }\n }\n }\n": types.OverviewDocument, "\n mutation ConnectSignIn($input: ConnectSignInInput!) {\n connectSignIn(input: $input)\n }\n": types.ConnectSignInDocument, "\n mutation SignOut {\n connectSignOut\n }\n": types.SignOutDocument, @@ -69,6 +70,10 @@ export function graphql(source: "\n mutation ArchiveAllNotifications {\n arc * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ export function graphql(source: "\n mutation DeleteNotification($id: String!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n"): (typeof documents)["\n mutation DeleteNotification($id: String!, $type: NotificationType!) {\n deleteNotification(id: $id, type: $type) {\n archive {\n total\n }\n }\n }\n"]; +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + */ +export function graphql(source: "\n mutation DeleteAllNotifications {\n deleteAllNotifications {\n archive {\n total\n }\n unread {\n total\n }\n }\n }\n"): (typeof documents)["\n mutation DeleteAllNotifications {\n deleteAllNotifications {\n archive {\n total\n }\n unread {\n total\n }\n }\n }\n"]; /** * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/web/composables/gql/graphql.ts b/web/composables/gql/graphql.ts index 9be88c132..da3c82ef9 100644 --- a/web/composables/gql/graphql.ts +++ b/web/composables/gql/graphql.ts @@ -1694,6 +1694,11 @@ export type DeleteNotificationMutationVariables = Exact<{ export type DeleteNotificationMutation = { __typename?: 'Mutation', deleteNotification: { __typename?: 'NotificationOverview', archive: { __typename?: 'NotificationCounts', total: number } } }; +export type DeleteAllNotificationsMutationVariables = Exact<{ [key: string]: never; }>; + + +export type DeleteAllNotificationsMutation = { __typename?: 'Mutation', deleteAllNotifications: { __typename?: 'NotificationOverview', archive: { __typename?: 'NotificationCounts', total: number }, unread: { __typename?: 'NotificationCounts', total: number } } }; + export type OverviewQueryVariables = Exact<{ [key: string]: never; }>; @@ -1752,6 +1757,7 @@ export const NotificationsDocument = {"kind":"Document","definitions":[{"kind":" export const ArchiveNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ArchiveNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archiveNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Notification"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"importance"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}}]}}]} as unknown as DocumentNode; export const ArchiveAllNotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ArchiveAllNotifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archiveAll"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"warning"}},{"kind":"Field","name":{"kind":"Name","value":"alert"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]} as unknown as DocumentNode; export const DeleteNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"type"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationType"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"type"},"value":{"kind":"Variable","name":{"kind":"Name","value":"type"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]} as unknown as DocumentNode; +export const DeleteAllNotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteAllNotifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteAllNotifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"archive"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}},{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]} as unknown as DocumentNode; export const OverviewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Overview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"overview"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unread"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"info"}},{"kind":"Field","name":{"kind":"Name","value":"warning"}},{"kind":"Field","name":{"kind":"Name","value":"alert"}},{"kind":"Field","name":{"kind":"Name","value":"total"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const ConnectSignInDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ConnectSignIn"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ConnectSignInInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectSignIn"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const SignOutDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SignOut"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectSignOut"}}]}}]} as unknown as DocumentNode; diff --git a/web/helpers/apollo-cache/index.ts b/web/helpers/apollo-cache/index.ts index fc0841354..655eda6e6 100644 --- a/web/helpers/apollo-cache/index.ts +++ b/web/helpers/apollo-cache/index.ts @@ -1,6 +1,6 @@ -import { InMemoryCache, type InMemoryCacheConfig } from "@apollo/client/core/index.js"; -import { mergeAndDedup } from "./merge"; -import { NotificationType } from "../../composables/gql/typename"; +import { InMemoryCache, type InMemoryCacheConfig } from '@apollo/client/core/index.js'; +import { NotificationType } from '../../composables/gql/typename'; +import { mergeAndDedup } from './merge'; /**------------------------------------------------------------------------ * ! Understanding Cache Type Policies @@ -36,7 +36,7 @@ const defaultCacheConfig: InMemoryCacheConfig = { * i.e. this means [filter.type, filter.importance], * not [filter, type, importance] *---------------------------------------------**/ - keyArgs: ["filter", ["type", "importance"]], + keyArgs: ['filter', ['type', 'importance']], /** * Merges incoming data into the correct offset position. @@ -72,7 +72,7 @@ const defaultCacheConfig: InMemoryCacheConfig = { * @returns the value to cache for this operation */ merge(_, incoming, { cache }) { - cache.evict({ fieldName: "notifications" }); + cache.evict({ fieldName: 'notifications' }); cache.gc(); // Run garbage collection to prevent orphans & incorrect cache state return incoming; // Return the incoming data so Apollo knows the result of the mutation }, @@ -101,6 +101,13 @@ const defaultCacheConfig: InMemoryCacheConfig = { return incoming; }, }, + deleteAllNotifications: { + merge(_, incoming, { cache }) { + cache.evict({ fieldName: 'notifications' }); + cache.gc(); + return incoming; + }, + }, }, }, },