From 705ed603bc00a7af225ec00b4d1f1b27ba8072c1 Mon Sep 17 00:00:00 2001 From: Rahim Rahman Date: Thu, 29 May 2025 13:20:19 -0600 Subject: [PATCH] feat(MM-64361): categoryChannels update ONLY when necessary (#8880) --- .../handlers/category.test.ts | 153 +++++++++++++----- .../server_data_operator/handlers/category.ts | 28 +++- 2 files changed, 138 insertions(+), 43 deletions(-) diff --git a/app/database/operator/server_data_operator/handlers/category.test.ts b/app/database/operator/server_data_operator/handlers/category.test.ts index 7a534551c..24fb4f446 100644 --- a/app/database/operator/server_data_operator/handlers/category.test.ts +++ b/app/database/operator/server_data_operator/handlers/category.test.ts @@ -12,47 +12,55 @@ import type ServerDataOperator from '..'; describe('*** Operator: Category Handlers tests ***', () => { let operator: ServerDataOperator; + let spyOnHandleRecords: jest.SpyInstance; beforeAll(async () => { await DatabaseManager.init(['baseHandler.test.com']); operator = DatabaseManager.serverDatabases['baseHandler.test.com']!.operator; + spyOnHandleRecords = jest.spyOn(operator, 'handleRecords'); }); - it('=> handleCategories: should write to the CATEGORY table', async () => { - expect.assertions(2); + beforeEach(() => { + jest.clearAllMocks(); + }); - const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords'); - const categories: Category[] = [ - { - id: 'kjlw9j1ttnxwig7tnqgebg7dtipno', - collapsed: false, - display_name: 'Test', - muted: false, - sort_order: 1, - sorting: 'recent', - team_id: '', - type: 'direct_messages', - }, - ]; + afterAll(async () => { + DatabaseManager.destroyServerDatabase('baseHandler.test.com'); + }); - await operator.handleCategories({ - categories, - prepareRecordsOnly: false, + describe('handleCategories', () => { + it('should write to the CATEGORY table', async () => { + expect.assertions(2); + + const categories: Category[] = [ + { + id: 'kjlw9j1ttnxwig7tnqgebg7dtipno', + collapsed: false, + display_name: 'Test', + muted: false, + sort_order: 1, + sorting: 'recent', + team_id: '', + type: 'direct_messages', + }, + ]; + + await operator.handleCategories({ + categories, + prepareRecordsOnly: false, + }); + + expect(spyOnHandleRecords).toHaveBeenCalledTimes(1); + expect(spyOnHandleRecords).toHaveBeenCalledWith({ + fieldName: 'id', + createOrUpdateRawValues: categories, + tableName: MM_TABLES.SERVER.CATEGORY, + prepareRecordsOnly: false, + transformer: transformCategoryRecord, + }, 'handleCategories'); }); - - expect(spyOnHandleRecords).toHaveBeenCalledTimes(1); - expect(spyOnHandleRecords).toHaveBeenCalledWith({ - fieldName: 'id', - createOrUpdateRawValues: categories, - tableName: MM_TABLES.SERVER.CATEGORY, - prepareRecordsOnly: false, - transformer: transformCategoryRecord, - }, 'handleCategories'); }); - it('=> handleCategoryChannels: should write to the CATEGORY_CHANNEL table', async () => { - expect.assertions(2); - - const spyOnHandleRecords = jest.spyOn(operator, 'handleRecords'); + describe('handleCategoryChannels', () => { const categoryChannels: CategoryChannel[] = [ { id: 'team_id-channel_id', @@ -62,18 +70,79 @@ describe('*** Operator: Category Handlers tests ***', () => { }, ]; - await operator.handleCategoryChannels({ - categoryChannels, - prepareRecordsOnly: false, + it('should write to the CATEGORY_CHANNEL table', async () => { + expect.assertions(2); + + await operator.handleCategoryChannels({ + categoryChannels, + prepareRecordsOnly: false, + }); + + expect(spyOnHandleRecords).toHaveBeenCalledTimes(1); + expect(spyOnHandleRecords).toHaveBeenCalledWith({ + fieldName: 'id', + createOrUpdateRawValues: categoryChannels, + tableName: MM_TABLES.SERVER.CATEGORY_CHANNEL, + prepareRecordsOnly: false, + transformer: transformCategoryChannelRecord, + }, 'handleCategoryChannels'); }); - expect(spyOnHandleRecords).toHaveBeenCalledTimes(1); - expect(spyOnHandleRecords).toHaveBeenCalledWith({ - fieldName: 'id', - createOrUpdateRawValues: categoryChannels, - tableName: MM_TABLES.SERVER.CATEGORY_CHANNEL, - prepareRecordsOnly: false, - transformer: transformCategoryChannelRecord, - }, 'handleCategoryChannels'); + it('should not update an existing record if no changes are made', async () => { + + await operator.handleCategoryChannels({ + categoryChannels, + prepareRecordsOnly: false, + }); + + expect(spyOnHandleRecords).not.toHaveBeenCalled(); + }); + + it('should update an existing record if changes are made', async () => { + + await operator.handleCategoryChannels({ + categoryChannels: [ + { + ...categoryChannels[0], + sort_order: 2, + }, + ], + prepareRecordsOnly: false, + }); + + expect(spyOnHandleRecords).toHaveBeenCalledTimes(1); + expect(spyOnHandleRecords).toHaveBeenCalledWith(expect.objectContaining({ + createOrUpdateRawValues: [ + expect.objectContaining({ + sort_order: 2, + }), + ], + }), 'handleCategoryChannels'); + }); + + it('should not update when missing id', async () => { + + const result = await operator.handleCategoryChannels({ + categoryChannels: [ + { + ...categoryChannels[0], + id: undefined, + }, + ], + prepareRecordsOnly: false, + }); + + expect(spyOnHandleRecords).not.toHaveBeenCalled(); + expect(result).toEqual([]); + }); + + it('should return empty array when no category channels are provided', async () => { + const result = await operator.handleCategoryChannels({ + categoryChannels: undefined, + prepareRecordsOnly: false, + }); + + expect(result).toEqual([]); + }); }); }); diff --git a/app/database/operator/server_data_operator/handlers/category.ts b/app/database/operator/server_data_operator/handlers/category.ts index 853193f38..b4358dbc8 100644 --- a/app/database/operator/server_data_operator/handlers/category.ts +++ b/app/database/operator/server_data_operator/handlers/category.ts @@ -98,7 +98,33 @@ const CategoryHandler = >(supe return []; } - const createOrUpdateRawValues = getUniqueRawsBy({raws: categoryChannels, key: 'id'}); + const uniqueRaws = getUniqueRawsBy({raws: categoryChannels, key: 'id'}) as CategoryChannel[]; + const ids = uniqueRaws.map((c) => c.id).filter((id): id is string => id !== undefined); + const db: Database = this.database; + const exists = await db.get(CATEGORY_CHANNEL).query( + Q.where('id', Q.oneOf(ids)), + ).fetch(); + const categoryChannelMap = new Map(exists.map((c) => [c.id, c])); + const createOrUpdateRawValues = uniqueRaws.reduce((res: CategoryChannel[], c) => { + if (!c.id) { + return res; + } + const e = categoryChannelMap.get(c.id); + if (!e) { + res.push(c); + } else if ( + e.categoryId !== c.category_id || + e.channelId !== c.channel_id || + e.sortOrder !== c.sort_order + ) { + res.push(c); + } + return res; + }, []); + + if (!createOrUpdateRawValues.length) { + return []; + } return this.handleRecords({ fieldName: 'id',