From 567141a60e39d0e3776baaacf5542bb0ccd13b3d Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Thu, 31 Mar 2022 15:18:35 +0300 Subject: [PATCH] [Gekidou DB]: Refactors thread and threads_in_team tables (#6100) * Refactors thread and threads_in_team tables The convention is that only threads being in the ThreadsInTeam will be shown in a thread list, and that only threads marked as loaded_in_global_threads are being shown in the All threads tab in the list. So when a thread arrives through different means, whether it's a WS event, or just fetching a (*new) thread by opening it in a channel, etc... we'll need to check if it's newer than any of the existing threads in the all threads list. If it is it will be added in the ThreadsInTeam and will be marked as loaded_in_global_threads: true. If it's not newer but it is an unread thread it will still be added in the ThreadsInTeam but marked as loaded_in_global_threads: false. This commit refactors `loaded_in_global_threads` field from the Thread table to the ThreadsInTeam table so that the above is possible. * Update tests * Addresses review comments --- app/database/models/server/team.ts | 15 +++++----- app/database/models/server/thread.ts | 3 -- app/database/models/server/thread_in_team.ts | 3 ++ .../handlers/thread.test.ts | 16 +++++++--- .../handlers/thread_in_team.ts | 29 +++++++++++++------ .../transformers/thread.ts | 5 +++- .../schema/server/table_schemas/thread.ts | 1 - .../server/table_schemas/thread_in_team.ts | 1 + app/database/schema/server/test.ts | 4 +-- types/database/models/servers/thread.d.ts | 3 -- .../models/servers/thread_in_team.d.ts | 3 ++ types/database/raw_values.d.ts | 1 + 12 files changed, 53 insertions(+), 31 deletions(-) diff --git a/app/database/models/server/team.ts b/app/database/models/server/team.ts index 74cb563849..965052fb81 100644 --- a/app/database/models/server/team.ts +++ b/app/database/models/server/team.ts @@ -110,19 +110,18 @@ export default class TeamModel extends Model implements TeamModelInterface { /** teamSearchHistories : All the searches performed on this team */ @children(TEAM_SEARCH_HISTORY) teamSearchHistories!: Query; - /** threads : All threads belonging to a team */ - @lazy threads = this.collections.get(THREAD).query( - Q.on(THREADS_IN_TEAM, 'team_id', this.id), - ); - /** threads : Threads list belonging to a team */ - @lazy threadsList = this.threads.extend( - Q.where('loadedInGlobalThreads', true), + @lazy threadsList = this.collections.get(THREAD).query( + Q.on(THREADS_IN_TEAM, Q.and( + Q.where('team_id', this.id), + Q.where('loadedInGlobalThreads', true), + )), Q.sortBy('last_reply_at', Q.desc), ); /** unreadThreadsList : Unread threads list belonging to a team */ - @lazy unreadThreadsList = this.threads.extend( + @lazy unreadThreadsList = this.collections.get(THREAD).query( + Q.on(THREADS_IN_TEAM, 'team_id', this.id), Q.where('unread_replies', Q.gt(0)), Q.sortBy('last_reply_at', Q.desc), ); diff --git a/app/database/models/server/thread.ts b/app/database/models/server/thread.ts index 3aa7ef2d84..a79406d8db 100644 --- a/app/database/models/server/thread.ts +++ b/app/database/models/server/thread.ts @@ -52,9 +52,6 @@ export default class ThreadModel extends Model implements ThreadModelInterface { /** unread_mentions : The number of mentions that have not been read by the user. */ @field('unread_mentions') unreadMentions!: number; - /** loaded_in_global_threads : Flag to differentiate the unread threads loaded for showing unread counts/mentions */ - @field('loaded_in_global_threads') loadedInGlobalThreads!: boolean; - /** participants : All the participants associated with this Thread */ @children(THREAD_PARTICIPANT) participants!: Query; diff --git a/app/database/models/server/thread_in_team.ts b/app/database/models/server/thread_in_team.ts index 4d6e6ee930..6bc8531d30 100644 --- a/app/database/models/server/thread_in_team.ts +++ b/app/database/models/server/thread_in_team.ts @@ -37,6 +37,9 @@ export default class ThreadInTeamModel extends Model implements ThreadInTeamMode /** team_id: Associated team identifier */ @field('team_id') teamId!: string; + /** loaded_in_global_threads : Flag to differentiate the unread threads loaded for showing unread counts/mentions */ + @field('loaded_in_global_threads') loadedInGlobalThreads!: boolean; + @immutableRelation(THREAD, 'thread_id') thread!: Relation; @immutableRelation(TEAM, 'team_id') team!: Relation; diff --git a/app/database/operator/server_data_operator/handlers/thread.test.ts b/app/database/operator/server_data_operator/handlers/thread.test.ts index 28ac4976fb..6ea62e7a2b 100644 --- a/app/database/operator/server_data_operator/handlers/thread.test.ts +++ b/app/database/operator/server_data_operator/handlers/thread.test.ts @@ -47,6 +47,7 @@ describe('*** Operator: Thread Handlers tests ***', () => { is_following: true, unread_replies: 0, unread_mentions: 0, + loaded_in_global_threads: false, }, ] as Thread[]; @@ -126,6 +127,7 @@ describe('*** Operator: Thread Handlers tests ***', () => { is_following: true, unread_replies: 0, unread_mentions: 0, + loaded_in_global_threads: true, }, { id: 'thread-2', @@ -138,6 +140,7 @@ describe('*** Operator: Thread Handlers tests ***', () => { is_following: true, unread_replies: 0, unread_mentions: 0, + loaded_in_global_threads: true, }, ] as Thread[]; @@ -151,8 +154,9 @@ describe('*** Operator: Thread Handlers tests ***', () => { id: 'user-1', }], is_following: true, - unread_replies: 0, + unread_replies: 2, unread_mentions: 0, + loaded_in_global_threads: false, }, ] as Thread[]; @@ -165,12 +169,16 @@ describe('*** Operator: Thread Handlers tests ***', () => { expect(spyOnPrepareRecords).toHaveBeenCalledWith({ createRaws: [{ - raw: {team_id: 'team_id_1', thread_id: 'thread-2'}, + raw: {team_id: 'team_id_1', thread_id: 'thread-2', loaded_in_global_threads: true}, }, { - raw: {team_id: 'team_id_2', thread_id: 'thread-2'}, + raw: {team_id: 'team_id_2', thread_id: 'thread-2', loaded_in_global_threads: false}, }], transformer: transformThreadInTeamRecord, - updateRaws: [], + updateRaws: [ + expect.objectContaining({ + raw: {team_id: 'team_id_1', thread_id: 'thread-1', loaded_in_global_threads: true}, + }), + ], tableName: 'ThreadsInTeam', }); }); diff --git a/app/database/operator/server_data_operator/handlers/thread_in_team.ts b/app/database/operator/server_data_operator/handlers/thread_in_team.ts index 0c07858fd3..590f7f8389 100644 --- a/app/database/operator/server_data_operator/handlers/thread_in_team.ts +++ b/app/database/operator/server_data_operator/handlers/thread_in_team.ts @@ -5,9 +5,9 @@ import {Q, Database} from '@nozbe/watermelondb'; import {MM_TABLES} from '@constants/database'; import {transformThreadInTeamRecord} from '@database/operator/server_data_operator/transformers/thread'; -import {getRawRecordPairs} from '@database/operator/utils/general'; +import {getRawRecordPairs, getValidRecordsForUpdate} from '@database/operator/utils/general'; -import type {HandleThreadInTeamArgs} from '@typings/database/database'; +import type {HandleThreadInTeamArgs, RecordPair} from '@typings/database/database'; import type ThreadInTeamModel from '@typings/database/models/servers/thread_in_team'; export interface ThreadInTeamHandlerMix { @@ -22,6 +22,7 @@ const ThreadInTeamHandler = (superclass: any) => class extends superclass { return []; } + const update: RecordPair[] = []; const create: ThreadInTeam[] = []; const teamIds = Object.keys(threadsMap); for await (const teamId of teamIds) { @@ -30,23 +31,33 @@ const ThreadInTeamHandler = (superclass: any) => class extends superclass { ).fetch(); for (const thread of threadsMap[teamId]) { - const exists = chunks.some((threadInTeam) => { + const chunk = chunks.find((threadInTeam) => { return threadInTeam.threadId === thread.id; }); - if (!exists) { + const newValue = { + thread_id: thread.id, + team_id: teamId, + loaded_in_global_threads: thread.loaded_in_global_threads, + }; + + // update record if loaded_in_global_threads is true + if (chunk && thread.loaded_in_global_threads) { + update.push(getValidRecordsForUpdate({ + tableName: THREADS_IN_TEAM, + newValue, + existingRecord: chunk, + })); + } else { // create chunk - create.push({ - thread_id: thread.id, - team_id: teamId, - }); + create.push(newValue); } } } const threadsInTeam = (await this.prepareRecords({ createRaws: getRawRecordPairs(create), - updateRaws: [], + updateRaws: update, transformer: transformThreadInTeamRecord, tableName: THREADS_IN_TEAM, })) as ThreadInTeamModel[]; diff --git a/app/database/operator/server_data_operator/transformers/thread.ts b/app/database/operator/server_data_operator/transformers/thread.ts index c42f3ea637..44dd6e010d 100644 --- a/app/database/operator/server_data_operator/transformers/thread.ts +++ b/app/database/operator/server_data_operator/transformers/thread.ts @@ -37,7 +37,6 @@ export const transformThreadRecord = ({action, database, value}: TransformerArgs thread.isFollowing = raw.is_following ?? record?.isFollowing; thread.unreadReplies = raw.unread_replies; thread.unreadMentions = raw.unread_mentions; - thread.loadedInGlobalThreads = raw.loaded_in_global_threads || record?.loadedInGlobalThreads; }; return prepareBaseRecord({ @@ -76,10 +75,14 @@ export const transformThreadParticipantRecord = ({action, database, value}: Tran export const transformThreadInTeamRecord = ({action, database, value}: TransformerArgs): Promise => { const raw = value.raw as ThreadInTeam; + const record = value.record as ThreadInTeamModel; const fieldsMapper = (threadInTeam: ThreadInTeamModel) => { threadInTeam.threadId = raw.thread_id; threadInTeam.teamId = raw.team_id; + + // if it's already loaded don't change it + threadInTeam.loadedInGlobalThreads = record?.loadedInGlobalThreads || raw.loaded_in_global_threads; }; return prepareBaseRecord({ diff --git a/app/database/schema/server/table_schemas/thread.ts b/app/database/schema/server/table_schemas/thread.ts index b7fdcb1ac2..87c93f2b78 100644 --- a/app/database/schema/server/table_schemas/thread.ts +++ b/app/database/schema/server/table_schemas/thread.ts @@ -16,6 +16,5 @@ export default tableSchema({ {name: 'reply_count', type: 'number'}, {name: 'unread_replies', type: 'number'}, {name: 'unread_mentions', type: 'number'}, - {name: 'loaded_in_global_threads', type: 'boolean'}, ], }); diff --git a/app/database/schema/server/table_schemas/thread_in_team.ts b/app/database/schema/server/table_schemas/thread_in_team.ts index aa0fb3d806..f93c418309 100644 --- a/app/database/schema/server/table_schemas/thread_in_team.ts +++ b/app/database/schema/server/table_schemas/thread_in_team.ts @@ -12,5 +12,6 @@ export default tableSchema({ columns: [ {name: 'team_id', type: 'string', isIndexed: true}, {name: 'thread_id', type: 'string', isIndexed: true}, + {name: 'loaded_in_global_threads', type: 'boolean', isIndexed: true}, ], }); diff --git a/app/database/schema/server/test.ts b/app/database/schema/server/test.ts index 673bff2e40..5a56fb5fca 100644 --- a/app/database/schema/server/test.ts +++ b/app/database/schema/server/test.ts @@ -472,7 +472,6 @@ describe('*** Test schema for SERVER database ***', () => { reply_count: {name: 'reply_count', type: 'number'}, unread_replies: {name: 'unread_replies', type: 'number'}, unread_mentions: {name: 'unread_mentions', type: 'number'}, - loaded_in_global_threads: {name: 'loaded_in_global_threads', type: 'boolean'}, }, columnArray: [ {name: 'last_reply_at', type: 'number'}, @@ -481,7 +480,6 @@ describe('*** Test schema for SERVER database ***', () => { {name: 'reply_count', type: 'number'}, {name: 'unread_replies', type: 'number'}, {name: 'unread_mentions', type: 'number'}, - {name: 'loaded_in_global_threads', type: 'boolean'}, ], }, [THREAD_PARTICIPANT]: { @@ -502,10 +500,12 @@ describe('*** Test schema for SERVER database ***', () => { columns: { team_id: {name: 'team_id', type: 'string', isIndexed: true}, thread_id: {name: 'thread_id', type: 'string', isIndexed: true}, + loaded_in_global_threads: {name: 'loaded_in_global_threads', type: 'boolean', isIndexed: true}, }, columnArray: [ {name: 'team_id', type: 'string', isIndexed: true}, {name: 'thread_id', type: 'string', isIndexed: true}, + {name: 'loaded_in_global_threads', type: 'boolean', isIndexed: true}, ], }, [USER]: { diff --git a/types/database/models/servers/thread.d.ts b/types/database/models/servers/thread.d.ts index a3612bfd26..02f399736d 100644 --- a/types/database/models/servers/thread.d.ts +++ b/types/database/models/servers/thread.d.ts @@ -35,9 +35,6 @@ export default class ThreadModel extends Model { /** unread_mentions : The number of mentions that are not read by the user. */ unreadMentions: number; - /** loaded_in_global_threads : Flag to differentiate the unread threads loaded for showing unread counts/mentions */ - loadedInGlobalThreads: boolean; - /** participants: All the participants of the thread */ participants: Query; diff --git a/types/database/models/servers/thread_in_team.d.ts b/types/database/models/servers/thread_in_team.d.ts index 9e35033a76..ce384d9bab 100644 --- a/types/database/models/servers/thread_in_team.d.ts +++ b/types/database/models/servers/thread_in_team.d.ts @@ -25,6 +25,9 @@ export default class ThreadInTeamModel extends Model { /** teamId: Associated thread identifier */ teamId: string; + /** loaded_in_global_threads : Flag to differentiate the unread threads loaded for showing unread counts/mentions */ + loadedInGlobalThreads: boolean; + /** thread : The related record to the parent Thread model */ thread: Relation; diff --git a/types/database/raw_values.d.ts b/types/database/raw_values.d.ts index 703ca78726..af70143593 100644 --- a/types/database/raw_values.d.ts +++ b/types/database/raw_values.d.ts @@ -85,6 +85,7 @@ type TermsOfService = { type ThreadInTeam = { thread_id: string; team_id: string; + loaded_in_global_threads: boolean; }; type RawValue =