From a30f22b9d9b9d0cb9a42e6fa6de0ac71e8a22fb7 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Dec 2023 11:48:57 +0400 Subject: [PATCH 01/14] update InboxItem model --- src/shared/models/InboxItem.ts | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/shared/models/InboxItem.ts b/src/shared/models/InboxItem.ts index cbf79c3326..e13cc74ff1 100644 --- a/src/shared/models/InboxItem.ts +++ b/src/shared/models/InboxItem.ts @@ -1,8 +1,32 @@ import { InboxItemType } from "@/shared/constants"; +import { CommonFeedType } from "./CommonFeed"; import { Timestamp } from "./Timestamp"; -export interface InboxItem { +export type InboxItemFeedItemData = + | { + feedItemType: CommonFeedType.Discussion; + discussionId: string; + } + | { + feedItemType: CommonFeedType.Proposal; + proposalId: string; + discussionId: string | null; + } + | { + feedItemType: Exclude< + CommonFeedType, + CommonFeedType.Discussion | CommonFeedType.Proposal + >; + }; + +export type InboxItem = { itemId: string; // id of feedItemFollow or ChatChannel updatedAt: Timestamp; - type: InboxItemType; -} +} & ( + | ({ + type: InboxItemType.FeedItemFollow; + } & InboxItemFeedItemData) + | { + type: InboxItemType.ChatChannel; + } +); From 72444f4768d35b7ada0b049935543427f4b17ef9 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Dec 2023 12:16:03 +0400 Subject: [PATCH 02/14] add method to subscribe to inbox sub-collection --- src/services/User.ts | 38 +++++++++++++++++++++++++++++++++++++ src/shared/models/index.tsx | 1 + 2 files changed, 39 insertions(+) diff --git a/src/services/User.ts b/src/services/User.ts index 18c7664f31..828609ad6b 100644 --- a/src/services/User.ts +++ b/src/services/User.ts @@ -8,6 +8,8 @@ import { Collection, CommonFeed, FeedItemFollowWithMetadata, + InboxItem, + SubCollections, Timestamp, User, } from "@/shared/models"; @@ -22,11 +24,18 @@ import Api from "./Api"; import { waitForUserToBeLoaded } from "./utils"; const converter = firestoreDataConverter(); +const inboxConverter = firestoreDataConverter(); class UserService { private getUsersCollection = () => firebase.firestore().collection(Collection.Users).withConverter(converter); + private getInboxSubCollection = (userId: string) => + this.getUsersCollection() + .doc(userId) + .collection(SubCollections.Inbox) + .withConverter(inboxConverter); + public updateUser = async (user: User): Promise => { const body: UpdateUserDto = { userId: user.uid, @@ -197,6 +206,35 @@ class UserService { hasMore: data.hasMore, }; }; + + public subscribeToNewInboxItems = ( + userId: string, + endBefore: Timestamp, + callback: ( + data: { + item: InboxItem; + statuses: { + isAdded: boolean; + isRemoved: boolean; + }; + }[], + ) => void, + ): UnsubscribeFunction => { + const query = this.getInboxSubCollection(userId) + .orderBy("updatedAt", "desc") + .endBefore(endBefore); + + return query.onSnapshot((snapshot) => { + const data = snapshot.docChanges().map((docChange) => ({ + item: docChange.doc.data(), + statuses: { + isAdded: docChange.type === "added", + isRemoved: docChange.type === "removed", + }, + })); + callback(data); + }); + }; } export default new UserService(); diff --git a/src/shared/models/index.tsx b/src/shared/models/index.tsx index fbb74b0468..26a3c1e922 100644 --- a/src/shared/models/index.tsx +++ b/src/shared/models/index.tsx @@ -15,6 +15,7 @@ export * from "./DiscussionMessage"; export * from "./Discussion"; export * from "./FeedItemFollow"; export * from "./Gender"; +export * from "./InboxItem"; export * from "./Invoices"; export * from "./Link"; export * from "./Payment"; From e6e1e535c4a5b7c31328e751132d467720145b6b Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Dec 2023 13:30:11 +0400 Subject: [PATCH 03/14] add method to fetch follow feed item by id --- src/services/FeedItemFollows.ts | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/services/FeedItemFollows.ts b/src/services/FeedItemFollows.ts index 98a3d99626..eb1751024a 100644 --- a/src/services/FeedItemFollows.ts +++ b/src/services/FeedItemFollows.ts @@ -1,4 +1,4 @@ -import { ApiEndpoint } from "@/shared/constants"; +import { ApiEndpoint, FirestoreDataSource } from "@/shared/constants"; import { DocChange } from "@/shared/constants/docChange"; import { UnsubscribeFunction } from "@/shared/interfaces"; import { FollowFeedItemPayload } from "@/shared/interfaces/api"; @@ -10,7 +10,7 @@ import { Timestamp, } from "@/shared/models"; import { firestoreDataConverter } from "@/shared/utils"; -import firebase from "@/shared/utils/firebase"; +import firebase, { isFirestoreCacheError } from "@/shared/utils/firebase"; import Api, { CancelToken } from "./Api"; import CommonService from "./Common"; import CommonFeedService from "./CommonFeed"; @@ -26,6 +26,33 @@ class FeedItemFollowsService { .collection(SubCollections.FeedItemFollows) .withConverter(converter); + public getFeedItemFollowDataById = async ( + userId: string, + feedItemFollowId: string, + source = FirestoreDataSource.Default, + ): Promise => { + try { + const snapshot = await this.getFeedItemFollowsSubCollection(userId) + .doc(feedItemFollowId) + .get({ source }); + + return snapshot?.data() || null; + } catch (error) { + if ( + source === FirestoreDataSource.Cache && + isFirestoreCacheError(error) + ) { + return this.getFeedItemFollowDataById( + userId, + feedItemFollowId, + FirestoreDataSource.Server, + ); + } + + throw error; + } + }; + public getUserFeedItemFollowData = async ( userId: string, feedItemId: string, From 48fc94f4b8475915bc01dccd13e768412281f988 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Dec 2023 13:51:45 +0400 Subject: [PATCH 04/14] add method to fetch chat channel by id --- src/services/Chat.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/services/Chat.ts b/src/services/Chat.ts index 581fad5d3a..df9b37ee8b 100644 --- a/src/services/Chat.ts +++ b/src/services/Chat.ts @@ -1,5 +1,5 @@ import { stringify } from "query-string"; -import { ApiEndpoint } from "@/shared/constants"; +import { ApiEndpoint, FirestoreDataSource } from "@/shared/constants"; import { DMUser, UnsubscribeFunction } from "@/shared/interfaces"; import { GetChatChannelMessagesResponse, @@ -20,7 +20,7 @@ import { firestoreDataConverter, getUserName, } from "@/shared/utils"; -import firebase from "@/shared/utils/firebase"; +import firebase, { isFirestoreCacheError } from "@/shared/utils/firebase"; import Api, { CancelToken } from "./Api"; const chatChannelConverter = firestoreDataConverter(); @@ -234,6 +234,31 @@ class ChatService { }); }; + public getChatChannelById = async ( + chatChannelId: string, + source = FirestoreDataSource.Default, + ): Promise => { + try { + const snapshot = await this.getChatChannelCollection() + .doc(chatChannelId) + .get({ source }); + + return snapshot?.data() || null; + } catch (error) { + if ( + source === FirestoreDataSource.Cache && + isFirestoreCacheError(error) + ) { + return this.getChatChannelById( + chatChannelId, + FirestoreDataSource.Server, + ); + } + + throw error; + } + }; + public getChatChannels = async (options: { participantId: string; startAt?: Timestamp; From 131129139db1349394b57e63f35a236bba12c953 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Dec 2023 14:18:03 +0400 Subject: [PATCH 05/14] change inbox items subscription to subscribe to inbox collection --- src/services/User.ts | 22 ++- src/shared/hooks/useCases/useInboxItems.ts | 194 +++++++++---------- src/store/states/inbox/saga/getInboxItems.ts | 4 +- 3 files changed, 114 insertions(+), 106 deletions(-) diff --git a/src/services/User.ts b/src/services/User.ts index 828609ad6b..c6d69cb8d2 100644 --- a/src/services/User.ts +++ b/src/services/User.ts @@ -144,7 +144,7 @@ class UserService { }); }; - public getInboxItems = async ( + public getInboxItemsWithMetadata = async ( options: { startAfter?: Timestamp | null; limit?: number; @@ -207,6 +207,26 @@ class UserService { }; }; + public getInboxItems = async (options: { + userId: string; + startAt?: Timestamp; + endAt?: Timestamp; + }): Promise => { + const { userId, startAt, endAt } = options; + let query = this.getInboxSubCollection(userId).orderBy("updatedAt", "desc"); + + if (startAt) { + query = query.startAt(startAt); + } + if (endAt) { + query = query.endAt(endAt); + } + + const snapshot = await query.get(); + + return snapshot.docs.map((doc) => doc.data()); + }; + public subscribeToNewInboxItems = ( userId: string, endBefore: Timestamp, diff --git a/src/shared/hooks/useCases/useInboxItems.ts b/src/shared/hooks/useCases/useInboxItems.ts index e52ed2c95a..3de476ad6d 100644 --- a/src/shared/hooks/useCases/useInboxItems.ts +++ b/src/shared/hooks/useCases/useInboxItems.ts @@ -7,15 +7,20 @@ import { CommonService, FeedItemFollowsService, Logger, + UserService, } from "@/services"; -import { InboxItemType } from "@/shared/constants"; +import { FirestoreDataSource, InboxItemType } from "@/shared/constants"; import { useIsMounted } from "@/shared/hooks"; -import { FeedLayoutItemWithFollowData } from "@/shared/interfaces"; +import { + ChatChannelLayoutItem, + FeedLayoutItemWithFollowData, +} from "@/shared/interfaces"; import { ChatChannel, CommonFeedType, FeedItemFollow, FeedItemFollowWithMetadata, + InboxItem, } from "@/shared/models"; import { inboxActions, InboxItems, selectInboxItems } from "@/store/states"; @@ -25,7 +30,7 @@ interface Return refetch: () => void; } -interface ItemBatch { +interface ItemBatch { item: T; statuses: { isAdded: boolean; @@ -35,8 +40,8 @@ interface ItemBatch { type ItemsBatch = ItemBatch[]; -const addMetadataToItemsBatch = async ( - batch: ItemsBatch, +const addMetadataToFeedItemFollowItemsBatch = async ( + batch: ItemBatch[], ): Promise[]> => { const data = await Promise.all( batch.map(async ({ item }) => { @@ -116,6 +121,65 @@ const addMetadataToItemsBatch = async ( })); }; +const addMetadataToChatChannelsBatch = ( + batch: ItemBatch[], +): ItemBatch[] => + batch + .filter(({ item: chatChannel }) => chatChannel.messageCount > 0) + .map(({ item: chatChannel, statuses }) => ({ + item: { + itemId: chatChannel.id, + type: InboxItemType.ChatChannel, + chatChannel: chatChannel, + }, + statuses, + })); + +const addMetadataToItemsBatch = async ( + userId: string, + batch: ItemsBatch, + feedItemIdsForNotListening: string[] = [], +): Promise[]> => { + const batchWithFeedItemFollowItems = ( + await Promise.all( + batch.map(async (batchItem) => { + const item = + batchItem.item.type === InboxItemType.FeedItemFollow + ? await FeedItemFollowsService.getFeedItemFollowDataById( + userId, + batchItem.item.itemId, + FirestoreDataSource.Cache, + ) + : null; + + return item ? { item, statuses: batchItem.statuses } : null; + }, []), + ) + ).filter((item): item is ItemBatch => Boolean(item)); + const batchWithChatChannelLayoutItems = ( + await Promise.all( + batch.map(async (batchItem) => { + const item = + batchItem.item.type === InboxItemType.ChatChannel + ? await ChatService.getChatChannelById( + batchItem.item.itemId, + FirestoreDataSource.Cache, + ) + : null; + + return item ? { item, statuses: batchItem.statuses } : null; + }, []), + ) + ).filter((item): item is ItemBatch => Boolean(item)); + const batchWithFeedLayoutItemWithFollowItems = + await addMetadataToFeedItemFollowItemsBatch(batchWithFeedItemFollowItems); + + return [ + ...batchWithFeedLayoutItemWithFollowItems, + ...addMetadataToChatChannelsBatch(batchWithChatChannelLayoutItems), + ].filter(({ item }) => !feedItemIdsForNotListening.includes(item.itemId)); +}; + export const useInboxItems = ( feedItemIdsForNotListening?: string[], options?: { unread?: boolean }, @@ -143,43 +207,9 @@ export const useInboxItems = ( fetch(); }; - const addNewChatChannels = ( + const addNewInboxItems = ( data: { - chatChannel: ChatChannel; - statuses: { - isAdded: boolean; - isRemoved: boolean; - }; - }[], - ) => { - const finalData = - feedItemIdsForNotListening && feedItemIdsForNotListening.length > 0 - ? data.filter( - (item) => !feedItemIdsForNotListening.includes(item.chatChannel.id), - ) - : data; - - if (finalData.length === 0) { - return; - } - - dispatch( - inboxActions.addNewInboxItems( - finalData.map((item) => ({ - item: { - itemId: item.chatChannel.id, - type: InboxItemType.ChatChannel, - chatChannel: item.chatChannel, - }, - statuses: item.statuses, - })), - ), - ); - }; - - const addNewFollowFeedItems = ( - data: { - item: FeedItemFollow; + item: InboxItem; statuses: { isAdded: boolean; isRemoved: boolean; @@ -190,17 +220,7 @@ export const useInboxItems = ( return; } - const filteredData = data.filter(({ item }) => - [CommonFeedType.Discussion, CommonFeedType.Proposal].includes(item.type), - ); - const finalData = - feedItemIdsForNotListening && feedItemIdsForNotListening.length > 0 - ? filteredData.filter( - (item) => - !feedItemIdsForNotListening.includes(item.item.feedItemId), - ) - : filteredData; - setNewItemsBatches((currentItems) => [...currentItems, finalData]); + setNewItemsBatches((currentItems) => [...currentItems, data]); }; useEffect(() => { @@ -213,35 +233,18 @@ export const useInboxItems = ( return; } - const [chatChannels, feedItemFollows] = await Promise.all([ - ChatService.getChatChannels({ - participantId: userId, - startAt, - endAt, - onlyWithMessages: true, - }), - FeedItemFollowsService.getFollowFeedItems({ - userId, - startAt, - endAt, - }), - ]); + const fetchedInboxItems = await UserService.getInboxItems({ + userId, + startAt, + endAt, + }); if (!isMounted()) { return; } - addNewChatChannels( - chatChannels.map((chatChannel) => ({ - chatChannel, - statuses: { - isAdded: false, - isRemoved: false, - }, - })), - ); - addNewFollowFeedItems( - feedItemFollows.map((item) => ({ + addNewInboxItems( + fetchedInboxItems.map((item) => ({ item, statuses: { isAdded: false, @@ -260,11 +263,11 @@ export const useInboxItems = ( return; } - const unsubscribe = ChatService.subscribeToNewUpdatedChatChannels( + const unsubscribe = UserService.subscribeToNewInboxItems( userId, inboxItems.firstDocTimestamp, (data) => { - addNewChatChannels(data); + addNewInboxItems(data); }, ); @@ -277,29 +280,7 @@ export const useInboxItems = ( ]); useEffect(() => { - if (!inboxItems.firstDocTimestamp || !userId || unread) { - return; - } - - const unsubscribe = - FeedItemFollowsService.subscribeToNewUpdatedFollowFeedItem( - userId, - inboxItems.firstDocTimestamp, - (data) => { - addNewFollowFeedItems(data); - }, - ); - - return unsubscribe; - }, [ - inboxItems.firstDocTimestamp, - userId, - feedItemIdsForNotListening, - unread, - ]); - - useEffect(() => { - if (!lastBatch) { + if (!lastBatch || !userId) { return; } if (lastBatch.length === 0) { @@ -309,8 +290,15 @@ export const useInboxItems = ( (async () => { try { - const finalData = await addMetadataToItemsBatch(lastBatch); - dispatch(inboxActions.addNewInboxItems(finalData)); + const finalData = await addMetadataToItemsBatch( + userId, + lastBatch, + feedItemIdsForNotListening, + ); + + if (finalData.length > 0) { + dispatch(inboxActions.addNewInboxItems(finalData)); + } } catch (error) { Logger.error(error); } finally { diff --git a/src/store/states/inbox/saga/getInboxItems.ts b/src/store/states/inbox/saga/getInboxItems.ts index 5d7c94b8b1..5c2b43260d 100644 --- a/src/store/states/inbox/saga/getInboxItems.ts +++ b/src/store/states/inbox/saga/getInboxItems.ts @@ -36,13 +36,13 @@ export function* getInboxItems( const currentItems = (yield select(selectInboxItems)) as InboxItems; const isFirstRequest = !currentItems.lastDocTimestamp; const { data, firstDocTimestamp, lastDocTimestamp, hasMore } = (yield call( - UserService.getInboxItems, + UserService.getInboxItemsWithMetadata, { startAfter: currentItems.lastDocTimestamp, limit, unread, }, - )) as Awaited>; + )) as Awaited>; const chatChannelItems = data.chatChannels .map((chatChannel) => ({ type: InboxItemType.ChatChannel, From 37d50935987d5187498858f1fb0d13b59720d905 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 4 Dec 2023 16:42:15 +0400 Subject: [PATCH 06/14] add filtering of middle fetched inbox items --- src/shared/hooks/useCases/useInboxItems.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/shared/hooks/useCases/useInboxItems.ts b/src/shared/hooks/useCases/useInboxItems.ts index 3de476ad6d..6ae21c6fe9 100644 --- a/src/shared/hooks/useCases/useInboxItems.ts +++ b/src/shared/hooks/useCases/useInboxItems.ts @@ -226,8 +226,11 @@ export const useInboxItems = ( useEffect(() => { (async () => { try { - const { firstDocTimestamp: startAt, lastDocTimestamp: endAt } = - inboxItems; + const { + data, + firstDocTimestamp: startAt, + lastDocTimestamp: endAt, + } = inboxItems; if (!userId || !startAt || !endAt) { return; @@ -243,8 +246,14 @@ export const useInboxItems = ( return; } + const filteredInboxItems = data + ? fetchedInboxItems.filter((fetchedItem) => + data.every((item) => item.itemId !== fetchedItem.itemId), + ) + : fetchedInboxItems; + addNewInboxItems( - fetchedInboxItems.map((item) => ({ + filteredInboxItems.map((item) => ({ item, statuses: { isAdded: false, From d88e03eeb7015ef52ab20756ad2ce77e4806282e Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Tue, 5 Dec 2023 15:35:33 +0400 Subject: [PATCH 07/14] fix inbox sub-collection name --- src/shared/models/shared.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/models/shared.tsx b/src/shared/models/shared.tsx index 60980a54d1..2858e1e69f 100644 --- a/src/shared/models/shared.tsx +++ b/src/shared/models/shared.tsx @@ -43,7 +43,7 @@ export enum SubCollections { ChatChannelUserUnique = "chatChannelUserUnique", CommonFeed = "commonFeed", CommonFeedObjectUserUnique = "commonFeedObjectUserUnique", - Inbox = "Inbox", + Inbox = "inbox", FeedItemFollows = "feedItemFollows", } From 6604f3333bfcd35fabf99fafd2177023d726cdae Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 10:36:51 +0400 Subject: [PATCH 08/14] add new fields to InboxItem --- src/services/User.ts | 7 +++++-- src/shared/models/InboxItem.ts | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/services/User.ts b/src/services/User.ts index c6d69cb8d2..5ecb0f997e 100644 --- a/src/services/User.ts +++ b/src/services/User.ts @@ -213,7 +213,10 @@ class UserService { endAt?: Timestamp; }): Promise => { const { userId, startAt, endAt } = options; - let query = this.getInboxSubCollection(userId).orderBy("updatedAt", "desc"); + let query = this.getInboxSubCollection(userId).orderBy( + "itemUpdatedAt", + "desc", + ); if (startAt) { query = query.startAt(startAt); @@ -241,7 +244,7 @@ class UserService { ) => void, ): UnsubscribeFunction => { const query = this.getInboxSubCollection(userId) - .orderBy("updatedAt", "desc") + .orderBy("itemUpdatedAt", "desc") .endBefore(endBefore); return query.onSnapshot((snapshot) => { diff --git a/src/shared/models/InboxItem.ts b/src/shared/models/InboxItem.ts index e13cc74ff1..43caa507b3 100644 --- a/src/shared/models/InboxItem.ts +++ b/src/shared/models/InboxItem.ts @@ -22,6 +22,8 @@ export type InboxItemFeedItemData = export type InboxItem = { itemId: string; // id of feedItemFollow or ChatChannel updatedAt: Timestamp; + itemUpdatedAt: Timestamp; + unread: boolean; } & ( | ({ type: InboxItemType.FeedItemFollow; From c783af9721bb5895770290fd26daccbf14308498 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 10:56:26 +0400 Subject: [PATCH 09/14] fix method used in refresh unread inbox items --- src/store/states/inbox/saga/refreshUnreadInboxItems.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/store/states/inbox/saga/refreshUnreadInboxItems.ts b/src/store/states/inbox/saga/refreshUnreadInboxItems.ts index f2f61f2681..9b01188219 100644 --- a/src/store/states/inbox/saga/refreshUnreadInboxItems.ts +++ b/src/store/states/inbox/saga/refreshUnreadInboxItems.ts @@ -19,13 +19,13 @@ export function* refreshUnreadInboxItems() { while (keepItemsFetching) { const { data, lastDocTimestamp, hasMore } = (yield call( - UserService.getInboxItems, + UserService.getInboxItemsWithMetadata, { startAfter, limit: 5, unread: true, }, - )) as Awaited>; + )) as Awaited>; const chatChannelItems = data.chatChannels .filter( (chatChannel) => From d3b8387ec7e6009ce5b478f978b751939c3e955b Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 11:21:29 +0400 Subject: [PATCH 10/14] add subscription to unread inbox items --- src/services/User.ts | 14 +++++++++++--- src/shared/hooks/useCases/useInboxItems.ts | 12 ++++++------ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/services/User.ts b/src/services/User.ts index 5ecb0f997e..4a325bed1e 100644 --- a/src/services/User.ts +++ b/src/services/User.ts @@ -231,8 +231,11 @@ class UserService { }; public subscribeToNewInboxItems = ( - userId: string, - endBefore: Timestamp, + options: { + userId: string; + endBefore: Timestamp; + unread?: boolean; + }, callback: ( data: { item: InboxItem; @@ -243,10 +246,15 @@ class UserService { }[], ) => void, ): UnsubscribeFunction => { - const query = this.getInboxSubCollection(userId) + const { userId, endBefore, unread } = options; + let query = this.getInboxSubCollection(userId) .orderBy("itemUpdatedAt", "desc") .endBefore(endBefore); + if (unread) { + query = query.where("unread", "==", true); + } + return query.onSnapshot((snapshot) => { const data = snapshot.docChanges().map((docChange) => ({ item: docChange.doc.data(), diff --git a/src/shared/hooks/useCases/useInboxItems.ts b/src/shared/hooks/useCases/useInboxItems.ts index 56e8bb2ee6..8626d4b3c8 100644 --- a/src/shared/hooks/useCases/useInboxItems.ts +++ b/src/shared/hooks/useCases/useInboxItems.ts @@ -17,13 +17,11 @@ import { } from "@/shared/interfaces"; import { ChatChannel, - CommonFeedType, FeedItemFollow, FeedItemFollowWithMetadata, InboxItem, } from "@/shared/models"; import { inboxActions, InboxItems, selectInboxItems } from "@/store/states"; -import { useUnreadInboxItems } from "./useUnreadInboxItems"; interface Return extends Pick { @@ -193,7 +191,6 @@ export const useInboxItems = ( const userId = user?.uid; const unread = options?.unread; const lastBatch = newItemsBatches[0]; - useUnreadInboxItems(options?.unread); const fetch = () => { dispatch( @@ -270,13 +267,16 @@ export const useInboxItems = ( }, []); useEffect(() => { - if (!inboxItems.firstDocTimestamp || !userId || unread) { + if (!inboxItems.firstDocTimestamp || !userId) { return; } const unsubscribe = UserService.subscribeToNewInboxItems( - userId, - inboxItems.firstDocTimestamp, + { + userId, + endBefore: inboxItems.firstDocTimestamp, + unread, + }, (data) => { addNewInboxItems(data); }, From 0271f04d9e3c7791308ee4011bbb44179a4faea2 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 11:34:05 +0400 Subject: [PATCH 11/14] delete logic related to unread inbox refresh --- .../hooks/useCases/useUnreadInboxItems.ts | 36 --------- src/store/states/inbox/actions.ts | 7 -- src/store/states/inbox/constants.ts | 5 -- src/store/states/inbox/saga/index.ts | 6 -- .../inbox/saga/refreshUnreadInboxItems.ts | 73 ------------------- 5 files changed, 127 deletions(-) delete mode 100644 src/shared/hooks/useCases/useUnreadInboxItems.ts delete mode 100644 src/store/states/inbox/saga/refreshUnreadInboxItems.ts diff --git a/src/shared/hooks/useCases/useUnreadInboxItems.ts b/src/shared/hooks/useCases/useUnreadInboxItems.ts deleted file mode 100644 index 6b3ae8f6c0..0000000000 --- a/src/shared/hooks/useCases/useUnreadInboxItems.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useEffect } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { usePreviousDistinct, useUpdateEffect } from "react-use"; -import { selectUserStreamsWithNotificationsAmount } from "@/pages/Auth/store/selectors"; -import { inboxActions } from "@/store/states"; - -export const useUnreadInboxItems = (unread?: boolean): void => { - const dispatch = useDispatch(); - const notificationsAmount = useSelector( - selectUserStreamsWithNotificationsAmount(), - ); - const previousNotificationsAmount = usePreviousDistinct(notificationsAmount); - - useUpdateEffect(() => { - if ( - !unread || - !notificationsAmount || - (typeof previousNotificationsAmount === "number" && - notificationsAmount < previousNotificationsAmount) - ) { - return; - } - - dispatch(inboxActions.refreshUnreadInboxItems.request()); - }, [notificationsAmount]); - - useEffect(() => { - return () => { - dispatch( - inboxActions.refreshUnreadInboxItems.cancel( - "Cancel unread inbox items refresh on unmount", - ), - ); - }; - }, []); -}; diff --git a/src/store/states/inbox/actions.ts b/src/store/states/inbox/actions.ts index aad2217412..d62a64992d 100644 --- a/src/store/states/inbox/actions.ts +++ b/src/store/states/inbox/actions.ts @@ -23,13 +23,6 @@ export const getInboxItems = createAsyncAction( string >(); -export const refreshUnreadInboxItems = createAsyncAction( - InboxActionType.REFRESH_UNREAD_INBOX_ITEMS, - InboxActionType.REFRESH_UNREAD_INBOX_ITEMS_SUCCESS, - InboxActionType.REFRESH_UNREAD_INBOX_ITEMS_FAILURE, - InboxActionType.REFRESH_UNREAD_INBOX_ITEMS_CANCEL, -)(); - export const addNewInboxItems = createStandardAction( InboxActionType.ADD_NEW_INBOX_ITEMS, )< diff --git a/src/store/states/inbox/constants.ts b/src/store/states/inbox/constants.ts index bc42bc1710..9a7b410e4c 100644 --- a/src/store/states/inbox/constants.ts +++ b/src/store/states/inbox/constants.ts @@ -6,11 +6,6 @@ export enum InboxActionType { GET_INBOX_ITEMS_FAILURE = "@INBOX/GET_INBOX_ITEMS_FAILURE", GET_INBOX_ITEMS_CANCEL = "@INBOX/GET_INBOX_ITEMS_CANCEL", - REFRESH_UNREAD_INBOX_ITEMS = "@INBOX/REFRESH_UNREAD_INBOX_ITEMS", - REFRESH_UNREAD_INBOX_ITEMS_SUCCESS = "@INBOX/REFRESH_UNREAD_INBOX_ITEMS_SUCCESS", - REFRESH_UNREAD_INBOX_ITEMS_FAILURE = "@INBOX/REFRESH_UNREAD_INBOX_ITEMS_FAILURE", - REFRESH_UNREAD_INBOX_ITEMS_CANCEL = "@INBOX/REFRESH_UNREAD_INBOX_ITEMS_CANCEL", - ADD_NEW_INBOX_ITEMS = "@INBOX/ADD_NEW_INBOX_ITEMS", UPDATE_INBOX_ITEM = "@INBOX/UPDATE_INBOX_ITEM", diff --git a/src/store/states/inbox/saga/index.ts b/src/store/states/inbox/saga/index.ts index b71cbe048b..f040cb9624 100644 --- a/src/store/states/inbox/saga/index.ts +++ b/src/store/states/inbox/saga/index.ts @@ -1,7 +1,6 @@ import { takeLatestWithCancel } from "@/shared/utils/saga"; import * as actions from "../actions"; import { getInboxItems } from "./getInboxItems"; -import { refreshUnreadInboxItems } from "./refreshUnreadInboxItems"; export function* mainSaga() { yield takeLatestWithCancel( @@ -9,9 +8,4 @@ export function* mainSaga() { actions.getInboxItems.cancel, getInboxItems, ); - yield takeLatestWithCancel( - actions.refreshUnreadInboxItems.request, - actions.refreshUnreadInboxItems.cancel, - refreshUnreadInboxItems, - ); } diff --git a/src/store/states/inbox/saga/refreshUnreadInboxItems.ts b/src/store/states/inbox/saga/refreshUnreadInboxItems.ts deleted file mode 100644 index 9b01188219..0000000000 --- a/src/store/states/inbox/saga/refreshUnreadInboxItems.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { call, put, select } from "redux-saga/effects"; -import { Logger, UserService } from "@/services"; -import { - ChatChannelToLayoutItemConverter, - FeedItemFollowToLayoutItemWithFollowDataConverter, -} from "@/shared/converters"; -import { Awaited, FeedLayoutItemWithFollowData } from "@/shared/interfaces"; -import { Timestamp } from "@/shared/models"; -import * as actions from "../actions"; -import { selectInboxItems } from "../selectors"; -import { InboxItems } from "../types"; - -export function* refreshUnreadInboxItems() { - try { - const currentItems = (yield select(selectInboxItems)) as InboxItems; - const newInboxItems: FeedLayoutItemWithFollowData[] = []; - let startAfter: Timestamp | null = null; - let keepItemsFetching = true; - - while (keepItemsFetching) { - const { data, lastDocTimestamp, hasMore } = (yield call( - UserService.getInboxItemsWithMetadata, - { - startAfter, - limit: 5, - unread: true, - }, - )) as Awaited>; - const chatChannelItems = data.chatChannels - .filter( - (chatChannel) => - chatChannel.messageCount > 0 && - (!currentItems.data || - currentItems.data.every( - (item) => item.itemId !== chatChannel.id, - )), - ) - .map((item) => ChatChannelToLayoutItemConverter.toTargetEntity(item)); - const feedItemFollowItems = data.feedItemFollows - .filter( - (feedItemFollow) => - !currentItems.data || - currentItems.data.every( - (item) => item.itemId !== feedItemFollow.id, - ), - ) - .map((item) => - FeedItemFollowToLayoutItemWithFollowDataConverter.toTargetEntity( - item, - ), - ); - newInboxItems.push(...chatChannelItems, ...feedItemFollowItems); - keepItemsFetching = hasMore; - startAfter = lastDocTimestamp; - } - - if (newInboxItems.length > 0) { - yield put( - actions.addNewInboxItems( - newInboxItems.map((item) => ({ - item, - statuses: { - isAdded: false, - isRemoved: false, - }, - })), - ), - ); - } - } catch (err) { - Logger.error(err); - } -} From 47ed4b3b8347305ef7d5176dfe20f6aec16ed5d8 Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 11:52:29 +0400 Subject: [PATCH 12/14] add subscription by inbox item's updatedAt --- src/services/User.ts | 5 +-- src/shared/hooks/useCases/useInboxItems.ts | 39 ++++++++++++++++++++-- src/store/states/inbox/actions.ts | 4 +++ src/store/states/inbox/constants.ts | 2 ++ src/store/states/inbox/reducer.ts | 5 +++ 5 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/services/User.ts b/src/services/User.ts index 4a325bed1e..bfd7e1af65 100644 --- a/src/services/User.ts +++ b/src/services/User.ts @@ -235,6 +235,7 @@ class UserService { userId: string; endBefore: Timestamp; unread?: boolean; + orderBy?: "itemUpdatedAt" | "updatedAt"; }, callback: ( data: { @@ -246,9 +247,9 @@ class UserService { }[], ) => void, ): UnsubscribeFunction => { - const { userId, endBefore, unread } = options; + const { userId, endBefore, unread, orderBy = "itemUpdatedAt" } = options; let query = this.getInboxSubCollection(userId) - .orderBy("itemUpdatedAt", "desc") + .orderBy(orderBy, "desc") .endBefore(endBefore); if (unread) { diff --git a/src/shared/hooks/useCases/useInboxItems.ts b/src/shared/hooks/useCases/useInboxItems.ts index 8626d4b3c8..496dad3443 100644 --- a/src/shared/hooks/useCases/useInboxItems.ts +++ b/src/shared/hooks/useCases/useInboxItems.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { selectUser } from "@/pages/Auth/store/selectors"; import { @@ -188,9 +188,11 @@ export const useInboxItems = ( const [newItemsBatches, setNewItemsBatches] = useState([]); const inboxItems = useSelector(selectInboxItems); const user = useSelector(selectUser()); + const inboxItemsRef = useRef(inboxItems); const userId = user?.uid; const unread = options?.unread; const lastBatch = newItemsBatches[0]; + inboxItemsRef.current = inboxItems; const fetch = () => { dispatch( @@ -281,8 +283,41 @@ export const useInboxItems = ( addNewInboxItems(data); }, ); + const unsubscribeFromUpdateAtSubscription = + UserService.subscribeToNewInboxItems( + { + userId, + endBefore: inboxItems.firstDocTimestamp, + unread, + orderBy: "updatedAt", + }, + (data) => { + const lastDocTimestampSeconds = + inboxItemsRef.current?.lastDocTimestamp?.seconds; + const currentInboxData = inboxItemsRef.current?.data; + const filteredData = + lastDocTimestampSeconds && currentInboxData + ? data.filter( + ({ item }) => + item.itemUpdatedAt.seconds >= lastDocTimestampSeconds && + !currentInboxData.some( + (currentItem) => currentItem.itemId === item.itemId, + ), + ) + : []; + + addNewInboxItems(filteredData); + + if (filteredData.length !== data.length) { + dispatch(inboxActions.setHasMoreInboxItems(true)); + } + }, + ); - return unsubscribe; + return () => { + unsubscribe(); + unsubscribeFromUpdateAtSubscription(); + }; }, [ inboxItems.firstDocTimestamp, userId, diff --git a/src/store/states/inbox/actions.ts b/src/store/states/inbox/actions.ts index d62a64992d..b8790864cc 100644 --- a/src/store/states/inbox/actions.ts +++ b/src/store/states/inbox/actions.ts @@ -67,6 +67,10 @@ export const resetInboxItems = createStandardAction( InboxActionType.RESET_INBOX_ITEMS, )(); +export const setHasMoreInboxItems = createStandardAction( + InboxActionType.SET_HAS_MORE_INBOX_ITEMS, +)(); + export const setSharedFeedItemId = createStandardAction( InboxActionType.SET_SHARED_FEED_ITEM_ID, )(); diff --git a/src/store/states/inbox/constants.ts b/src/store/states/inbox/constants.ts index 9a7b410e4c..123079defb 100644 --- a/src/store/states/inbox/constants.ts +++ b/src/store/states/inbox/constants.ts @@ -15,6 +15,8 @@ export enum InboxActionType { RESET_INBOX_ITEMS = "@INBOX/RESET_INBOX_ITEMS", + SET_HAS_MORE_INBOX_ITEMS = "@INBOX/SET_HAS_MORE_INBOX_ITEMS", + SET_SHARED_FEED_ITEM_ID = "@INBOX/SET_SHARED_FEED_ITEM_ID", SET_SHARED_INBOX_ITEM = "@INBOX/SET_SHARED_INBOX_ITEM", diff --git a/src/store/states/inbox/reducer.ts b/src/store/states/inbox/reducer.ts index c6d488bebb..4888cf07ec 100644 --- a/src/store/states/inbox/reducer.ts +++ b/src/store/states/inbox/reducer.ts @@ -558,6 +558,11 @@ export const reducer = createReducer(INITIAL_INBOX_STATE) nextState.nextChatChannelItemId = null; }), ) + .handleAction(actions.setHasMoreInboxItems, (state, { payload }) => + produce(state, (nextState) => { + nextState.items.hasMore = payload; + }), + ) .handleAction(actions.setSharedFeedItemId, (state, { payload }) => produce(state, (nextState) => { nextState.sharedFeedItemId = payload; From 46de5f6fc3b96974cbca1745b4d81f3f2c619afe Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 11 Dec 2023 14:35:29 +0400 Subject: [PATCH 13/14] remove labeler workflow related files --- .github/labeler.yml | 18 -------- .github/labels.json | 69 ----------------------------- .github/workflows/label-labeler.yml | 11 ----- .github/workflows/label-sync.yml | 11 ----- 4 files changed, 109 deletions(-) delete mode 100644 .github/labeler.yml delete mode 100644 .github/labels.json delete mode 100644 .github/workflows/label-labeler.yml delete mode 100644 .github/workflows/label-sync.yml diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index 4962b4beb7..0000000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,18 +0,0 @@ -actions: - - .github/workflows/**/* - -firebase: - - packages/firebase/**/* - -web: - - packages/web/**/* - -types: - - packages/types/**/* - -app: - - packages/app/**/* - -admin: - - packages/admin/**/* - - packages/firebase/functions/src/core/admin/**/* \ No newline at end of file diff --git a/.github/labels.json b/.github/labels.json deleted file mode 100644 index 68ac30dd93..0000000000 --- a/.github/labels.json +++ /dev/null @@ -1,69 +0,0 @@ -[ - { - "name": "firebase", - "color": "5319E7", - "description": "The item is related to the firebase code" - }, - { - "name": "admin", - "color": "5319E7", - "description": "The item is related to the admin frontend or backend code" - }, - { - "name": "app", - "color": "0E8A16", - "description": "The item is related to the app code" - }, - { - "name": "web", - "color": "FBCA04", - "description": "The item is related to the web app code" - }, - { - "name": "types", - "color": "0052CC", - "description": "The item is related to the types code" - }, - { - "name": "tests", - "color": "006B75", - "description": "The item is related to the tests code" - }, - { - "name": "actions", - "color": "FBCA04", - "description": "The item is related to the actions code" - }, - - { - "name": "bug", - "color": "B60205", - "description": "The item fixes bug or is bug report" - }, - { - "name": "feature", - "color": "C5DEF5", - "description": "The item is about new feature" - }, - { - "name": "enhancement", - "color": "C2E0C6", - "description": "The item is enhancement to the development experience, user experience or speed optimization" - }, - - { - "name": "priority:low", - "color": "D4C5F9", - "description": "The item is with low priority" - }, - { - "name": "priority:normal", - "color": "1D76DB", - "description": "The item is with normal priority" - }, - { - "name": "priority:high", - "color": "B60205", - "description": "The item is with high priority" - } -] \ No newline at end of file diff --git a/.github/workflows/label-labeler.yml b/.github/workflows/label-labeler.yml deleted file mode 100644 index a4ea44c5a5..0000000000 --- a/.github/workflows/label-labeler.yml +++ /dev/null @@ -1,11 +0,0 @@ -name: "Pull Request Labeler" -on: pull_request - -jobs: - labels: - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@main - with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" - sync-babels: true \ No newline at end of file diff --git a/.github/workflows/label-sync.yml b/.github/workflows/label-sync.yml deleted file mode 100644 index af0dfd5fbd..0000000000 --- a/.github/workflows/label-sync.yml +++ /dev/null @@ -1,11 +0,0 @@ -on: workflow_dispatch -name: Sync the labels -jobs: - labels: - name: LabelSync - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@1.0.0 - - uses: lannonbr/issue-label-manager-action@2.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 984e7d0975fb10254de520818c9daf1dd57fedcb Mon Sep 17 00:00:00 2001 From: Andrey Mikhadyuk Date: Mon, 18 Dec 2023 12:35:46 +0400 Subject: [PATCH 14/14] fix inbox data update in the loop --- src/shared/hooks/useCases/useInboxItems.ts | 87 +++++++++++++--------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/src/shared/hooks/useCases/useInboxItems.ts b/src/shared/hooks/useCases/useInboxItems.ts index 496dad3443..9355d4258e 100644 --- a/src/shared/hooks/useCases/useInboxItems.ts +++ b/src/shared/hooks/useCases/useInboxItems.ts @@ -20,6 +20,7 @@ import { FeedItemFollow, FeedItemFollowWithMetadata, InboxItem, + Timestamp, } from "@/shared/models"; import { inboxActions, InboxItems, selectInboxItems } from "@/store/states"; @@ -186,6 +187,7 @@ export const useInboxItems = ( const dispatch = useDispatch(); const isMounted = useIsMounted(); const [newItemsBatches, setNewItemsBatches] = useState([]); + const [lastUpdatedAt, setLastUpdatedAt] = useState(null); const inboxItems = useSelector(selectInboxItems); const user = useSelector(selectUser()); const inboxItemsRef = useRef(inboxItems); @@ -279,47 +281,62 @@ export const useInboxItems = ( endBefore: inboxItems.firstDocTimestamp, unread, }, + addNewInboxItems, + ); + + return unsubscribe; + }, [ + inboxItems.firstDocTimestamp, + userId, + feedItemIdsForNotListening, + unread, + ]); + + useEffect(() => { + const endBefore = lastUpdatedAt || inboxItems.firstDocTimestamp; + + if (!endBefore || !userId) { + return; + } + + const unsubscribe = UserService.subscribeToNewInboxItems( + { + userId, + endBefore, + unread, + orderBy: "updatedAt", + }, (data) => { - addNewInboxItems(data); + const lastDocTimestampSeconds = + inboxItemsRef.current?.lastDocTimestamp?.seconds; + const currentInboxData = inboxItemsRef.current?.data; + const filteredData = + lastDocTimestampSeconds && currentInboxData + ? data.filter( + ({ item }) => + item.itemUpdatedAt.seconds >= lastDocTimestampSeconds && + !currentInboxData.some( + (currentItem) => currentItem.itemId === item.itemId, + ), + ) + : []; + + if (data[0]) { + setLastUpdatedAt(data[0].item.updatedAt); + } + + addNewInboxItems(filteredData); + + if (filteredData.length !== data.length) { + dispatch(inboxActions.setHasMoreInboxItems(true)); + } }, ); - const unsubscribeFromUpdateAtSubscription = - UserService.subscribeToNewInboxItems( - { - userId, - endBefore: inboxItems.firstDocTimestamp, - unread, - orderBy: "updatedAt", - }, - (data) => { - const lastDocTimestampSeconds = - inboxItemsRef.current?.lastDocTimestamp?.seconds; - const currentInboxData = inboxItemsRef.current?.data; - const filteredData = - lastDocTimestampSeconds && currentInboxData - ? data.filter( - ({ item }) => - item.itemUpdatedAt.seconds >= lastDocTimestampSeconds && - !currentInboxData.some( - (currentItem) => currentItem.itemId === item.itemId, - ), - ) - : []; - - addNewInboxItems(filteredData); - - if (filteredData.length !== data.length) { - dispatch(inboxActions.setHasMoreInboxItems(true)); - } - }, - ); - return () => { - unsubscribe(); - unsubscribeFromUpdateAtSubscription(); - }; + return unsubscribe; }, [ inboxItems.firstDocTimestamp, + lastUpdatedAt, userId, feedItemIdsForNotListening, unread,