forked from Ivasoft/mattermost-mobile
[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
This commit is contained in:
@@ -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<TeamSearchHistoryModel>;
|
||||
|
||||
/** threads : All threads belonging to a team */
|
||||
@lazy threads = this.collections.get<ThreadModel>(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<ThreadModel>(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<ThreadModel>(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),
|
||||
);
|
||||
|
||||
@@ -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<ThreadParticipantModel>;
|
||||
|
||||
|
||||
@@ -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<ThreadModel>;
|
||||
|
||||
@immutableRelation(TEAM, 'team_id') team!: Relation<TeamModel>;
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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<ThreadInTeamModel> => {
|
||||
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({
|
||||
|
||||
@@ -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'},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -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]: {
|
||||
|
||||
3
types/database/models/servers/thread.d.ts
vendored
3
types/database/models/servers/thread.d.ts
vendored
@@ -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<ThreadParticipantsModel>;
|
||||
|
||||
|
||||
@@ -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<ThreadModel>;
|
||||
|
||||
|
||||
1
types/database/raw_values.d.ts
vendored
1
types/database/raw_values.d.ts
vendored
@@ -85,6 +85,7 @@ type TermsOfService = {
|
||||
type ThreadInTeam = {
|
||||
thread_id: string;
|
||||
team_id: string;
|
||||
loaded_in_global_threads: boolean;
|
||||
};
|
||||
|
||||
type RawValue =
|
||||
|
||||
Reference in New Issue
Block a user