From 60098e03d794237b63cd2d1effd2eb4eb2c37156 Mon Sep 17 00:00:00 2001 From: Ivan Kalachikov Date: Fri, 30 Aug 2024 15:01:59 +0800 Subject: [PATCH] chore: replace updateQueries with update (#1258) --- client-app/core/api/graphql/config/cache.ts | 7 +- client-app/core/api/graphql/consts.ts | 1 + .../mutations/clearAllPushMessages/index.ts | 39 ++++++----- .../markAllPushMessagesRead/index.ts | 52 ++++++++------ .../markAllPushMessagesUnread/index.ts | 69 +++++++++++++------ .../mutations/markPushMessageRead/index.ts | 65 +++++++++++------ .../mutations/markPushMessageUnread/index.ts | 65 +++++++++++------ .../queries/getPushMessages/index.ts | 23 ++++++- .../api/graphql/push-messages/typePolices.ts | 13 ++++ client-app/core/constants/notifications.ts | 6 ++ client-app/router/routes/account.ts | 5 +- .../account/components/account-navigation.vue | 5 +- .../header/_internal/bottom-header.vue | 10 +-- .../header/_internal/mobile-header.vue | 10 +-- .../push-messages/components/push-message.vue | 2 +- .../composables/usePushMessage.ts | 6 +- .../composables/usePushMessages.ts | 18 +++-- .../useWebPushNotifications/index.ts | 8 ++- 18 files changed, 262 insertions(+), 142 deletions(-) create mode 100644 client-app/core/api/graphql/push-messages/typePolices.ts diff --git a/client-app/core/api/graphql/config/cache.ts b/client-app/core/api/graphql/config/cache.ts index 99121ca86..1da33e1d3 100644 --- a/client-app/core/api/graphql/config/cache.ts +++ b/client-app/core/api/graphql/config/cache.ts @@ -1,4 +1,5 @@ import { InMemoryCache } from "@apollo/client/core"; +import { pushMessagesTypePolices } from "../push-messages/typePolices"; export const cache = new InMemoryCache({ typePolicies: { @@ -70,11 +71,7 @@ export const cache = new InMemoryCache({ }, Query: { fields: { - pushMessages: { - merge(existing, incoming) { - return { ...existing, ...incoming }; - }, - }, + ...pushMessagesTypePolices, }, }, }, diff --git a/client-app/core/api/graphql/consts.ts b/client-app/core/api/graphql/consts.ts index 1f3599d92..8733f0e65 100644 --- a/client-app/core/api/graphql/consts.ts +++ b/client-app/core/api/graphql/consts.ts @@ -1,3 +1,4 @@ export const HTTP_ENDPOINT_URL = "/graphql"; export const WEBSOCKETS_ENDPOINT_URL = `${location.protocol === "https:" ? "wss" : "ws"}://${location.host}${HTTP_ENDPOINT_URL}`; export const WEBSOCKETS_ENDPOINT_TIMEOUT = 55 * 1000; // 55 seconds +export const ROOT_QUERY_CACHE_ID = "ROOT_QUERY"; diff --git a/client-app/core/api/graphql/push-messages/mutations/clearAllPushMessages/index.ts b/client-app/core/api/graphql/push-messages/mutations/clearAllPushMessages/index.ts index a03558932..a9181df25 100644 --- a/client-app/core/api/graphql/push-messages/mutations/clearAllPushMessages/index.ts +++ b/client-app/core/api/graphql/push-messages/mutations/clearAllPushMessages/index.ts @@ -1,7 +1,9 @@ import { useApolloClient } from "@vue/apollo-composable"; import { useMutation } from "@/core/api/graphql/composables/useMutation"; +import { ROOT_QUERY_CACHE_ID } from "@/core/api/graphql/consts"; +import { PUSH_MESSAGES_CACHE_ID } from "@/core/constants/notifications"; import { ClearAllPushMessagesDocument, OperationNames } from "../../types"; -import type { GetPushMessagesQuery } from "../../types"; +import type { PushMessageConnection } from "../../types"; export function useClearAllPushMessages() { const { client } = useApolloClient(); @@ -9,24 +11,23 @@ export function useClearAllPushMessages() { optimisticResponse: { clearAllPushMessages: true, }, - updateQueries: { - [OperationNames.Query.GetPushMessages]: (previousQueryResult, { mutationResult }) => { - const pushMessagesQueryResult = previousQueryResult as GetPushMessagesQuery; - if (mutationResult.data?.clearAllPushMessages) { - return { - ...pushMessagesQueryResult, - pushMessages: { - items: [], - totalCount: 0, - }, - unreadCount: { - totalCount: 0, - }, - } satisfies GetPushMessagesQuery; - } else { - return { ...pushMessagesQueryResult }; - } - }, + update(cache, updateResult) { + if (!updateResult.data?.clearAllPushMessages) { + return; + } + const cacheData = cache.extract() as Record; + const pushMessagesIds = Object.keys(cacheData[ROOT_QUERY_CACHE_ID] ?? {}).filter(([key]) => + key.startsWith(PUSH_MESSAGES_CACHE_ID), + ); + pushMessagesIds?.forEach((id) => { + cache.modify>({ + id: id, + fields: { + items: () => [], + totalCount: () => 0, + }, + }); + }); }, // Just in case we did something wrong in cache refetchQueries: [OperationNames.Query.GetPushMessages], diff --git a/client-app/core/api/graphql/push-messages/mutations/markAllPushMessagesRead/index.ts b/client-app/core/api/graphql/push-messages/mutations/markAllPushMessagesRead/index.ts index 49de673b2..586560cbb 100644 --- a/client-app/core/api/graphql/push-messages/mutations/markAllPushMessagesRead/index.ts +++ b/client-app/core/api/graphql/push-messages/mutations/markAllPushMessagesRead/index.ts @@ -1,33 +1,45 @@ import { useMutation } from "@/core/api/graphql/composables/useMutation"; +import { + PUSH_MESSAGE_CACHE_ID, + UNREAD_COUNT_CACHE_ID, + UNREAD_COUNT_WITH_HIDDEN_CACHE_ID, +} from "@/core/constants/notifications"; import { MarkAllPushMessagesReadDocument, OperationNames } from "../../types"; -import type { GetPushMessagesQuery } from "../../types"; +import type { PushMessageConnection } from "../../types"; export function useMarkAllPushMessagesRead() { return useMutation(MarkAllPushMessagesReadDocument, { optimisticResponse: { markAllPushMessagesRead: true, }, - updateQueries: { - [OperationNames.Query.GetPushMessages]: (previousQueryResult, { mutationResult }) => { - const pushMessagesQueryResult = previousQueryResult as GetPushMessagesQuery; - if (mutationResult.data?.markAllPushMessagesRead) { - return { - ...pushMessagesQueryResult, - pushMessages: { - items: pushMessagesQueryResult.pushMessages?.items?.map((pushMessage) => ({ - ...pushMessage, - isRead: true, - })), - totalCount: pushMessagesQueryResult.pushMessages?.items?.length ?? 0, - }, - unreadCount: { - totalCount: 0, + update(cache, result) { + if (!result.data?.markAllPushMessagesRead) { + return; + } + const cacheData = cache.extract() as Record; + + // update all push messages to be read + Object.keys(cacheData).forEach((id) => { + if (id.startsWith(PUSH_MESSAGE_CACHE_ID)) { + cache.modify({ + id: id, + fields: { + isRead: () => true, }, - } satisfies GetPushMessagesQuery; - } else { - return { ...pushMessagesQueryResult }; + }); } - }, + }); + + cache.modify>>({ + fields: { + [UNREAD_COUNT_CACHE_ID]: () => ({ + totalCount: 0, + }), + [UNREAD_COUNT_WITH_HIDDEN_CACHE_ID]: () => ({ + totalCount: 0, + }), + }, + }); }, // Just in case we did something wrong in cache refetchQueries: [OperationNames.Query.GetPushMessages], diff --git a/client-app/core/api/graphql/push-messages/mutations/markAllPushMessagesUnread/index.ts b/client-app/core/api/graphql/push-messages/mutations/markAllPushMessagesUnread/index.ts index 66b7d30ae..5b5260a71 100644 --- a/client-app/core/api/graphql/push-messages/mutations/markAllPushMessagesUnread/index.ts +++ b/client-app/core/api/graphql/push-messages/mutations/markAllPushMessagesUnread/index.ts @@ -1,34 +1,61 @@ import { useMutation } from "@/core/api/graphql/composables/useMutation"; +import { ROOT_QUERY_CACHE_ID } from "@/core/api/graphql/consts"; +import { + PUSH_MESSAGE_CACHE_ID, + PUSH_MESSAGES_CACHE_ID, + UNREAD_COUNT_CACHE_ID, + UNREAD_COUNT_WITH_HIDDEN_CACHE_ID, +} from "@/core/constants/notifications"; import { MarkAllPushMessagesUnreadDocument, OperationNames } from "../../types"; -import type { GetPushMessagesQuery } from "../../types"; +import type { PushMessageConnection } from "../../types"; export function useMarkAllPushMessagesUnread() { return useMutation(MarkAllPushMessagesUnreadDocument, { optimisticResponse: { markAllPushMessagesUnread: true, }, - updateQueries: { - [OperationNames.Query.GetPushMessages]: (previousQueryResult, { mutationResult, queryVariables }) => { - const pushMessagesQueryResult = previousQueryResult as GetPushMessagesQuery; - const { withHidden } = queryVariables; - if (mutationResult.data?.markAllPushMessagesUnread) { - return { - ...pushMessagesQueryResult, - pushMessages: { - items: pushMessagesQueryResult.pushMessages?.items?.map((pushMessage) => ({ - ...pushMessage, - isRead: false, - })), - totalCount: pushMessagesQueryResult.pushMessages?.items?.length ?? 0, - }, - unreadCount: { - totalCount: !withHidden ? pushMessagesQueryResult.pushMessages?.items?.length : 0, + update(cache, result) { + if (!result.data?.markAllPushMessagesUnread) { + return; + } + const cacheData = cache.extract() as Record; + + // update all push messages to be unread + Object.keys(cacheData).forEach((id) => { + if (id.startsWith(PUSH_MESSAGE_CACHE_ID)) { + cache.modify({ + id: id, + fields: { + isRead: () => false, }, - } satisfies GetPushMessagesQuery; - } else { - return { ...pushMessagesQueryResult }; + }); } - }, + }); + + // read current messages count and messages with hidden count from cache + const rootQueryEntries = Object.entries((cacheData[ROOT_QUERY_CACHE_ID] ?? {}) as Record); + + const messages = rootQueryEntries.find( + ([key]) => + key.startsWith(PUSH_MESSAGES_CACHE_ID) && key.includes("after") && !key.includes('"withHidden":true'), + ) as [string, PushMessageConnection] | undefined; + const messagesWithHidden = rootQueryEntries.find( + ([key]) => key.startsWith(PUSH_MESSAGES_CACHE_ID) && key.includes("after") && key.includes('"withHidden":true'), + ) as [string, PushMessageConnection] | undefined; + + const messagesCount = messages?.[1]?.totalCount ?? 0; + const messagesWithHiddenCount = messagesWithHidden?.[1]?.totalCount ?? 0; + + cache.modify>>({ + fields: { + [UNREAD_COUNT_CACHE_ID]: () => ({ + totalCount: messagesCount, + }), + [UNREAD_COUNT_WITH_HIDDEN_CACHE_ID]: () => ({ + totalCount: messagesWithHiddenCount, + }), + }, + }); }, // Just in case we did something wrong in cache refetchQueries: [OperationNames.Query.GetPushMessages], diff --git a/client-app/core/api/graphql/push-messages/mutations/markPushMessageRead/index.ts b/client-app/core/api/graphql/push-messages/mutations/markPushMessageRead/index.ts index 68f4e84da..bb7c71546 100644 --- a/client-app/core/api/graphql/push-messages/mutations/markPushMessageRead/index.ts +++ b/client-app/core/api/graphql/push-messages/mutations/markPushMessageRead/index.ts @@ -1,35 +1,56 @@ import { useMutation } from "@/core/api/graphql/composables/useMutation"; +import { + PUSH_MESSAGE_CACHE_ID, + UNREAD_COUNT_CACHE_ID, + UNREAD_COUNT_WITH_HIDDEN_CACHE_ID, +} from "@/core/constants/notifications"; import { MarkPushMessageReadDocument, OperationNames, PushMessageFragmentDoc } from "../../types"; -import type { GetPushMessagesQuery } from "../../types"; +import type { PushMessageConnection, PushMessageType } from "../../types"; -export function useMarkPushMessageRead(optimistic = true) { +type UnreadCountType = Pick; + +export function useMarkPushMessageRead() { return useMutation(MarkPushMessageReadDocument, { optimisticResponse: { markPushMessageRead: true, }, update(cache, result, { variables }) { - if (result.data?.markPushMessageRead) { - cache.updateFragment( - { - id: `PushMessageType:${variables?.command?.messageId}`, - fragment: PushMessageFragmentDoc, - }, - (pushMessage) => ({ ...pushMessage!, isRead: true }), - ); + if (!result.data?.markPushMessageRead) { + return; } - }, - updateQueries: (!optimistic && {}) || { - [OperationNames.Query.GetPushMessages]: (previousQueryResult) => { - const pushMessagesQueryResult = previousQueryResult as GetPushMessagesQuery; - return { - ...pushMessagesQueryResult, - unreadCount: { - totalCount: pushMessagesQueryResult.unreadCount?.totalCount - ? pushMessagesQueryResult.unreadCount.totalCount - 1 - : 0, + + let message: PushMessageType | null = null; + + cache.updateFragment( + { + id: `${PUSH_MESSAGE_CACHE_ID}${variables?.command?.messageId}`, + fragment: PushMessageFragmentDoc, + }, + (pushMessage) => { + message = pushMessage; + return pushMessage ? { ...pushMessage, isRead: true } : null; + }, + ); + + cache.modify({ + fields: { + [UNREAD_COUNT_CACHE_ID]: (value) => { + const unreadCount = value as UnreadCountType; + if (message?.isHidden) { + return unreadCount; + } + return { + totalCount: unreadCount.totalCount ? unreadCount.totalCount - 1 : 0, + }; + }, + [UNREAD_COUNT_WITH_HIDDEN_CACHE_ID]: (value) => { + const unreadCountWithHidden = value as UnreadCountType; + return { + totalCount: unreadCountWithHidden.totalCount ? unreadCountWithHidden.totalCount - 1 : 0, + }; }, - } satisfies GetPushMessagesQuery; - }, + }, + }); }, // Just in case we did something wrong in cache refetchQueries: [OperationNames.Query.GetPushMessages], diff --git a/client-app/core/api/graphql/push-messages/mutations/markPushMessageUnread/index.ts b/client-app/core/api/graphql/push-messages/mutations/markPushMessageUnread/index.ts index df6d14bf8..2493e1f03 100644 --- a/client-app/core/api/graphql/push-messages/mutations/markPushMessageUnread/index.ts +++ b/client-app/core/api/graphql/push-messages/mutations/markPushMessageUnread/index.ts @@ -1,35 +1,56 @@ import { useMutation } from "@/core/api/graphql/composables/useMutation"; +import { + PUSH_MESSAGE_CACHE_ID, + UNREAD_COUNT_CACHE_ID, + UNREAD_COUNT_WITH_HIDDEN_CACHE_ID, +} from "@/core/constants/notifications"; import { MarkPushMessageUnreadDocument, OperationNames, PushMessageFragmentDoc } from "../../types"; -import type { GetPushMessagesQuery } from "../../types"; +import type { PushMessageConnection, PushMessageType } from "../../types"; -export function useMarkPushMessageUnread(optimistic = true) { +type UnreadCountType = Pick; + +export function useMarkPushMessageUnread() { return useMutation(MarkPushMessageUnreadDocument, { optimisticResponse: { markPushMessageUnread: true, }, update(cache, result, { variables }) { - if (result.data?.markPushMessageUnread) { - cache.updateFragment( - { - id: `PushMessageType:${variables?.command?.messageId}`, - fragment: PushMessageFragmentDoc, - }, - (pushMessage) => ({ ...pushMessage!, isRead: false }), - ); + if (!result.data?.markPushMessageUnread) { + return; } - }, - updateQueries: (!optimistic && {}) || { - [OperationNames.Query.GetPushMessages]: (previousQueryResult) => { - const pushMessagesQueryResult = previousQueryResult as GetPushMessagesQuery; - return { - ...pushMessagesQueryResult, - unreadCount: { - totalCount: pushMessagesQueryResult.unreadCount?.totalCount - ? pushMessagesQueryResult.unreadCount.totalCount + 1 - : 1, + + let message: PushMessageType | null = null; + + cache.updateFragment( + { + id: `${PUSH_MESSAGE_CACHE_ID}${variables?.command?.messageId}`, + fragment: PushMessageFragmentDoc, + }, + (pushMessage) => { + message = pushMessage; + return pushMessage ? { ...pushMessage, isRead: false } : null; + }, + ); + + cache.modify({ + fields: { + [UNREAD_COUNT_CACHE_ID]: (value) => { + const unreadCount = value as UnreadCountType; + if (message?.isHidden) { + return unreadCount; + } + return { + totalCount: (unreadCount.totalCount ?? 0) + 1, + }; + }, + [UNREAD_COUNT_WITH_HIDDEN_CACHE_ID]: (value) => { + const unreadCountWithHidden = value as UnreadCountType; + return { + totalCount: (unreadCountWithHidden.totalCount ?? 0) + 1, + }; }, - } satisfies GetPushMessagesQuery; - }, + }, + }); }, // Just in case we did something wrong in cache refetchQueries: [OperationNames.Query.GetPushMessages], diff --git a/client-app/core/api/graphql/push-messages/queries/getPushMessages/index.ts b/client-app/core/api/graphql/push-messages/queries/getPushMessages/index.ts index bfbc69f0a..142b043fc 100644 --- a/client-app/core/api/graphql/push-messages/queries/getPushMessages/index.ts +++ b/client-app/core/api/graphql/push-messages/queries/getPushMessages/index.ts @@ -1,18 +1,30 @@ import { useQuery } from "@vue/apollo-composable"; +import { toValue } from "vue"; import { GetPushMessagesDocument, OnPushMessageCreatedDocument } from "../../types"; import type { GetPushMessagesQueryVariables } from "../../types"; import type { MaybeRefOrGetter } from "vue"; +let subscribed = false; +let subscribedWithHidden = false; + export function useGetPushMessages(payload: MaybeRefOrGetter) { const result = useQuery(GetPushMessagesDocument, payload, { fetchPolicy: "cache-and-network" }); + const payloadValue = toValue(payload); + if ((!payloadValue.withHidden && subscribed) || (payloadValue.withHidden && subscribedWithHidden)) { + return result; + } result.subscribeToMore({ document: OnPushMessageCreatedDocument, updateQuery: (previousQueryResult, { subscriptionData }) => { if (!subscriptionData.data) { return previousQueryResult; } + const newPushMessage = subscriptionData.data.pushMessageCreated; const items = previousQueryResult.pushMessages?.items ?? []; + const unreadCount = previousQueryResult.unreadCount?.totalCount; + const unreadCountWithHidden = previousQueryResult.unreadCountWithHidden?.totalCount; + return { ...previousQueryResult, pushMessages: { @@ -20,10 +32,19 @@ export function useGetPushMessages(payload: MaybeRefOrGetter + args?.after + ? ["withHidden", "unreadOnly", "after", "first", "cultureName"] + : ["withHidden", "unreadOnly", "after", "first"], + merge(existing, incoming) { + return { ...existing, ...incoming }; + }, + } as FieldPolicy, +}; diff --git a/client-app/core/constants/notifications.ts b/client-app/core/constants/notifications.ts index b55261058..42b010424 100644 --- a/client-app/core/constants/notifications.ts +++ b/client-app/core/constants/notifications.ts @@ -1 +1,7 @@ export const DEFAULT_NOTIFICATION_DURATION = 15000; + +export const UNREAD_COUNT_CACHE_ID = 'pushMessages:{"unreadOnly":true}'; +export const UNREAD_COUNT_WITH_HIDDEN_CACHE_ID = 'pushMessages:{"withHidden":true,"unreadOnly":true}'; + +export const PUSH_MESSAGE_CACHE_ID = "PushMessageType:"; +export const PUSH_MESSAGES_CACHE_ID = "pushMessages:"; diff --git a/client-app/router/routes/account.ts b/client-app/router/routes/account.ts index 348cf05ec..0b7d990bf 100644 --- a/client-app/router/routes/account.ts +++ b/client-app/router/routes/account.ts @@ -1,6 +1,6 @@ import { useThemeContext } from "@/core/composables"; import { useUser } from "@/shared/account"; -import { usePushMessages } from "@/shared/push-messages/composables/usePushMessages"; +import { isActive as isPushMessagesActive } from "@/shared/push-messages/composables/usePushMessages"; import type { RouteRecordRaw } from "vue-router"; const Dashboard = () => import("@/pages/account/dashboard.vue"); @@ -119,8 +119,7 @@ export const accountRoutes: RouteRecordRaw[] = [ name: "Notifications", component: Notifications, beforeEnter(_to, _from, next) { - const { isActive } = usePushMessages(); - if (themeContext.value?.settings?.push_messages_enabled && isActive) { + if (isPushMessagesActive.value) { next(); } else { next({ name: "Dashboard" }); diff --git a/client-app/shared/account/components/account-navigation.vue b/client-app/shared/account/components/account-navigation.vue index 27c962c41..ddb48a795 100644 --- a/client-app/shared/account/components/account-navigation.vue +++ b/client-app/shared/account/components/account-navigation.vue @@ -95,7 +95,7 @@ /> route.name === "ListDetails"); const isOrdersPage = eagerComputed(() => route.name === "Orders"); diff --git a/client-app/shared/layout/components/header/_internal/bottom-header.vue b/client-app/shared/layout/components/header/_internal/bottom-header.vue index c273ed860..7b46ac3fa 100644 --- a/client-app/shared/layout/components/header/_internal/bottom-header.vue +++ b/client-app/shared/layout/components/header/_internal/bottom-header.vue @@ -44,10 +44,7 @@