diff --git a/app/products/playbooks/actions/local/version.test.ts b/app/products/playbooks/actions/local/version.test.ts index 5009b4d8a..b473df9f7 100644 --- a/app/products/playbooks/actions/local/version.test.ts +++ b/app/products/playbooks/actions/local/version.test.ts @@ -4,15 +4,29 @@ import {SYSTEM_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; import {PLAYBOOK_TABLES} from '@playbooks/constants/database'; +import {getMyChannel} from '@queries/servers/channel'; import {querySystemValue} from '@queries/servers/system'; +import TestHelper from '@test/test_helper'; +import {updateLastPlaybookRunsFetchAt} from './channel'; import {setPlaybooksVersion} from './version'; import type ServerDataOperator from '@database/operator/server_data_operator'; const serverUrl = 'baseHandler.test.com'; +const channelId = 'channelid'; let operator: ServerDataOperator; +const channel: Channel = TestHelper.fakeChannel({ + id: channelId, + team_id: 'teamid1', +}); + +const channelMember: ChannelMembership = TestHelper.fakeChannelMember({ + id: 'id', + channel_id: channelId, +}); + beforeEach(async () => { await DatabaseManager.init([serverUrl]); operator = DatabaseManager.serverDatabases[serverUrl]!.operator; @@ -50,6 +64,11 @@ describe('setPlaybooksVersion', () => { it('should purge playbooks when version is empty', async () => { const database = operator.database; + + await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); + await operator.handleMyChannel({channels: [channel], myChannels: [channelMember], prepareRecordsOnly: false}); + await updateLastPlaybookRunsFetchAt(serverUrl, channelId, 1234); + jest.spyOn(database.adapter, 'unsafeExecute').mockImplementation(() => { return Promise.resolve(); }); @@ -65,10 +84,18 @@ describe('setPlaybooksVersion', () => { [`DELETE FROM ${PLAYBOOK_TABLES.PLAYBOOK_CHECKLIST_ITEM}`, []], ], }); + + const myChannel = await getMyChannel(operator.database, channelId); + expect(myChannel?.lastPlaybookRunsFetchAt).toBe(0); }); it('should not purge playbooks when version is not empty', async () => { const database = operator.database; + + await operator.handleChannel({channels: [channel], prepareRecordsOnly: false}); + await operator.handleMyChannel({channels: [channel], myChannels: [channelMember], prepareRecordsOnly: false}); + await updateLastPlaybookRunsFetchAt(serverUrl, channelId, 1234); + jest.spyOn(database.adapter, 'unsafeExecute').mockImplementation(() => { return Promise.resolve(); }); @@ -78,6 +105,8 @@ describe('setPlaybooksVersion', () => { expect(data).toBe(true); expect(database.adapter.unsafeExecute).not.toHaveBeenCalled(); + const myChannel = await getMyChannel(operator.database, channelId); + expect(myChannel?.lastPlaybookRunsFetchAt).toBe(1234); }); it('should handle purge playbooks errors', async () => { diff --git a/app/products/playbooks/actions/local/version.ts b/app/products/playbooks/actions/local/version.ts index 81efe064e..226f05550 100644 --- a/app/products/playbooks/actions/local/version.ts +++ b/app/products/playbooks/actions/local/version.ts @@ -1,9 +1,14 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {SYSTEM_IDENTIFIERS} from '@constants/database'; +import {Q} from '@nozbe/watermelondb'; + +import {MM_TABLES, SYSTEM_IDENTIFIERS} from '@constants/database'; import DatabaseManager from '@database/manager'; import {PLAYBOOK_TABLES} from '@playbooks/constants/database'; +import EphemeralStore from '@store/ephemeral_store'; + +import type {MyChannelModel} from '@database/models/server'; export const setPlaybooksVersion = async (serverUrl: string, version: string) => { try { @@ -21,6 +26,8 @@ export const setPlaybooksVersion = async (serverUrl: string, version: string) => if (error) { return {error}; } + + EphemeralStore.clearChannelPlaybooksSynced(serverUrl); } return {data: true}; @@ -32,14 +39,25 @@ export const setPlaybooksVersion = async (serverUrl: string, version: string) => const purgePlaybooks = async (serverUrl: string) => { try { const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl); - await database.write(() => { - return database.adapter.unsafeExecute({ + const models = await database.collections.get(MM_TABLES.SERVER.MY_CHANNEL).query(Q.where('last_playbook_runs_fetch_at', Q.gt(0))); + + await database.write(async () => { + await database.adapter.unsafeExecute({ sqls: [ [`DELETE FROM ${PLAYBOOK_TABLES.PLAYBOOK_RUN}`, []], [`DELETE FROM ${PLAYBOOK_TABLES.PLAYBOOK_CHECKLIST}`, []], [`DELETE FROM ${PLAYBOOK_TABLES.PLAYBOOK_CHECKLIST_ITEM}`, []], ], }); + + // Process each model sequentially to prevent overwhelming the event loop with excessive promises + // and to avoid overloading the database with concurrent write operations, especially when handling a large number of channels. + for await (const model of models) { + await model.update((channel) => { + channel.lastPlaybookRunsFetchAt = 0; + return channel; + }); + } }); } catch (error) { return {error};