Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 43 additions & 21 deletions web/components/Notifications/Item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import {
CheckBadgeIcon,
ExclamationTriangleIcon,
LinkIcon,
TrashIcon,
} from "@heroicons/vue/24/solid";
import { useMutation } from "@vue/apollo-composable";
import type { NotificationFragmentFragment } from "~/composables/gql/graphql";
import { archiveNotification as archiveMutation } from "./graphql/notification.query";

import { NotificationType } from "~/composables/gql/graphql";
import {
archiveNotification as archiveMutation,
deleteNotification as deleteMutation,
} from "./graphql/notification.query";

const props = defineProps<NotificationFragmentFragment>();

Expand All @@ -33,8 +39,20 @@ const icon = computed<{ component: Component; color: string } | null>(() => {
return null;
});

const { mutate: archive, loading } = useMutation(archiveMutation, {
variables: { id: props.id },
const archive = reactive(
useMutation(archiveMutation, {
variables: { id: props.id },
})
);

const deleteNotification = reactive(
useMutation(deleteMutation, {
variables: { id: props.id, type: props.type },
})
);

const mutationError = computed(() => {
return archive.error?.message ?? deleteNotification.error?.message;
});
</script>

Expand Down Expand Up @@ -71,31 +89,35 @@ const { mutate: archive, loading } = useMutation(archiveMutation, {
<p class="text-secondary-foreground">{{ description }}</p>
</div>

<p v-if="mutationError" class="text-red-600">Error: {{ mutationError }}</p>

<div class="flex justify-end items-baseline gap-2">
<a v-if="link" :href="link">
<Button type="button" variant="outline" size="xs">
<LinkIcon class="size-3 mr-1 text-muted-foreground/80" />
<span class="text-sm text-muted-foreground mt-0.5">View</span>
</Button>
</a>
<TooltipProvider>
<Tooltip>
<TooltipTrigger as-child>
<Button
:disabled="loading"
class="relative z-20 rounded"
size="xs"
@click="archive"
>
<ArchiveBoxIcon class="size-3 mr-1" />
<span class="text-sm mt-0.5">Archive</span>
</Button>
</TooltipTrigger>
<TooltipContent>
<p>Archive</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<Button
v-if="type === NotificationType.Unread"
:disabled="archive.loading"
class="relative z-20 rounded"
size="xs"
@click="archive.mutate"
>
<ArchiveBoxIcon class="size-3 mr-1" />
<span class="text-sm mt-0.5">Archive</span>
</Button>
<Button
v-if="type === NotificationType.Archive"
:disabled="deleteNotification.loading"
class="relative z-20 rounded"
size="xs"
@click="deleteNotification.mutate"
>
<TrashIcon class="size-3 mr-1" />
<span class="text-sm mt-0.5">Delete</span>
</Button>
</div>
</div>
</template>
10 changes: 10 additions & 0 deletions web/components/Notifications/graphql/notification.query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,13 @@ export const archiveAllNotifications = graphql(/* GraphQL */ `
}
}
`);

export const deleteNotification = graphql(/* GraphQL */ `
mutation DeleteNotification($id: String!, $type: NotificationType!) {
deleteNotification(id: $id, type: $type) {
archive {
total
}
}
}
`);
5 changes: 5 additions & 0 deletions web/composables/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const documents = {
"\n query Notifications($filter: NotificationFilter!) {\n notifications {\n id\n list(filter: $filter) {\n ...NotificationFragment\n }\n }\n }\n": types.NotificationsDocument,
"\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 ConnectSignIn($input: ConnectSignInInput!) {\n connectSignIn(input: $input)\n }\n": types.ConnectSignInDocument,
"\n mutation SignOut {\n connectSignOut\n }\n": types.SignOutDocument,
"\n fragment PartialCloud on Cloud {\n error\n apiKey {\n valid\n error\n }\n cloud {\n status\n error\n }\n minigraphql {\n status\n error\n }\n relay {\n status\n error\n }\n }\n": types.PartialCloudFragmentDoc,
Expand Down Expand Up @@ -57,6 +58,10 @@ export function graphql(source: "\n mutation ArchiveNotification($id: String!)
* 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 ArchiveAllNotifications {\n archiveAll {\n unread {\n total\n }\n archive {\n info\n warning\n alert\n total\n }\n }\n }\n"): (typeof documents)["\n mutation ArchiveAllNotifications {\n archiveAll {\n unread {\n total\n }\n archive {\n info\n warning\n alert\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 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.
*/
Expand Down
10 changes: 10 additions & 0 deletions web/composables/gql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -821,6 +821,7 @@ export type Node = {
export type Notification = Node & {
__typename?: 'Notification';
description: Scalars['String']['output'];
formattedTimestamp?: Maybe<Scalars['String']['output']>;
id: Scalars['ID']['output'];
importance: Importance;
link?: Maybe<Scalars['String']['output']>;
Expand Down Expand Up @@ -1683,6 +1684,14 @@ export type ArchiveAllNotificationsMutationVariables = Exact<{ [key: string]: ne

export type ArchiveAllNotificationsMutation = { __typename?: 'Mutation', archiveAll: { __typename?: 'NotificationOverview', unread: { __typename?: 'NotificationCounts', total: number }, archive: { __typename?: 'NotificationCounts', info: number, warning: number, alert: number, total: number } } };

export type DeleteNotificationMutationVariables = Exact<{
id: Scalars['String']['input'];
type: NotificationType;
}>;


export type DeleteNotificationMutation = { __typename?: 'Mutation', deleteNotification: { __typename?: 'NotificationOverview', archive: { __typename?: 'NotificationCounts', total: number } } };

export type ConnectSignInMutationVariables = Exact<{
input: ConnectSignInInput;
}>;
Expand Down Expand Up @@ -1734,6 +1743,7 @@ export const PartialCloudFragmentDoc = {"kind":"Document","definitions":[{"kind"
export const NotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Notifications"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"list"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"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<NotificationsQuery, NotificationsQueryVariables>;
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<ArchiveNotificationMutation, ArchiveNotificationMutationVariables>;
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<ArchiveAllNotificationsMutation, ArchiveAllNotificationsMutationVariables>;
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<DeleteNotificationMutation, DeleteNotificationMutationVariables>;
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<ConnectSignInMutation, ConnectSignInMutationVariables>;
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<SignOutMutation, SignOutMutationVariables>;
export const serverStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"serverState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PartialCloud"}}]}},{"kind":"Field","name":{"kind":"Name","value":"config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}},{"kind":"Field","name":{"kind":"Name","value":"info"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"os"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hostname"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updateExpiration"}}]}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialCloud"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"minigraphql"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode<serverStateQuery, serverStateQueryVariables>;
Expand Down
6 changes: 6 additions & 0 deletions web/composables/gql/typename.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* The magic 'Notification' prefix comes from inspecting the apollo cache.
* I think it comes from the __typename when apollo caches an object (by default)
*/
export const NotificationType = "Notification";

29 changes: 28 additions & 1 deletion web/helpers/apollo-cache/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { InMemoryCache, type InMemoryCacheConfig } from "@apollo/client/core";
import { mergeAndDedup } from "./merge";
import { NotificationType } from "../../composables/gql/typename";

/**------------------------------------------------------------------------
* ! Understanding Cache Type Policies
Expand Down Expand Up @@ -50,7 +51,9 @@ const defaultCacheConfig: InMemoryCacheConfig = {
*/
merge(existing = [], incoming, { args }) {
const offset = args?.filter?.offset ?? 0;
return mergeAndDedup(existing, incoming, (item) => item.__ref, { offset });
return mergeAndDedup(existing, incoming, (item) => item.__ref, {
offset,
});
},
},
},
Expand All @@ -74,6 +77,30 @@ const defaultCacheConfig: InMemoryCacheConfig = {
return incoming; // Return the incoming data so Apollo knows the result of the mutation
},
},
deleteNotification: {
/**
* Ensures that a deleted notification is removed from the cache +
* any cached items that reference it
*
* @param _ - Unused parameter representing the existing cache value.
* @param incoming - The result from the server after the mutation.
* @param cache - The Apollo cache instance.
* @param args - Arguments passed to the mutation, expected to contain the `id` of the notification to evict.
* @returns The incoming result to be cached.
*/
merge(_, incoming, { cache, args }) {
if (args?.id) {
const id = cache.identify({
id: args.id,
__typename: NotificationType,
});
cache.evict({ id });
}
// Removes references to evicted notification, preventing dangling references
cache.gc();
return incoming;
},
},
},
},
},
Expand Down