From 7a05798910138b22e1b754d8c674f0c73c76f4ce Mon Sep 17 00:00:00 2001 From: Daneryl Date: Wed, 5 Feb 2025 06:32:33 +0100 Subject: [PATCH] processDocument is now an awaited process when running inside a transaction --- app/api/entities/managerFunctions.ts | 13 +++++----- app/api/entities/routes.js | 7 ++++++ app/api/utils/specs/withTransaction.spec.ts | 28 ++++++++++++++++++++- app/api/utils/withTransaction.ts | 7 +++--- 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/app/api/entities/managerFunctions.ts b/app/api/entities/managerFunctions.ts index 98bb520c08..961df186df 100644 --- a/app/api/entities/managerFunctions.ts +++ b/app/api/entities/managerFunctions.ts @@ -13,6 +13,7 @@ import { MetadataObjectSchema } from 'shared/types/commonTypes'; import { EntityWithFilesSchema } from 'shared/types/entityType'; import { TypeOfFile } from 'shared/types/fileSchema'; import { FileAttachment } from './entitySavingManager'; +import { tenants } from 'api/tenants'; const prepareNewFiles = async ( entity: EntityWithFilesSchema, @@ -197,21 +198,21 @@ const saveFiles = async ( ); if (documentsToProcess.length) { - // eslint-disable-next-line @typescript-eslint/no-floating-promises - Promise.allSettled( + const documentsBeingProcessed = Promise.allSettled( documentsToProcess.map(async document => processDocument(entity.sharedId!, document)) ).then(results => { results .filter(result => result.status === 'rejected') - .map(rejected => { - const { reason } = rejected as PromiseRejectedResult; - handleError(reason); - }); + .map(rejected => handleError(rejected.reason)); if (socketEmiter) { socketEmiter('documentProcessed', entity.sharedId!); } }); + + if (tenants.current().featureFlags?.v1_transactions) { + await documentsBeingProcessed; + } } if (attachments.length || documents.length) { diff --git a/app/api/entities/routes.js b/app/api/entities/routes.js index c716c86a2f..991178ecac 100644 --- a/app/api/entities/routes.js +++ b/app/api/entities/routes.js @@ -9,6 +9,7 @@ import { thesauri } from '../thesauri/thesauri'; import { parseQuery, validation } from '../utils'; import date from '../utils/date'; import entities from './entities'; +import { tenants } from 'api/tenants'; async function updateThesauriWithEntity(entity, req) { const template = await templates.getById(entity.template); @@ -97,6 +98,12 @@ export default app => { return req.body.entity ? saveResult : entity; }); res.json(result); + if (tenants.current().featureFlags.v1_transactions) { + req.emitToSessionSocket( + 'documentProcessed', + req.body.entity ? result.entity.sharedId : result.sharedId + ); + } } catch (e) { next(e); } diff --git a/app/api/utils/specs/withTransaction.spec.ts b/app/api/utils/specs/withTransaction.spec.ts index 5b1022888d..ca79b3eb61 100644 --- a/app/api/utils/specs/withTransaction.spec.ts +++ b/app/api/utils/specs/withTransaction.spec.ts @@ -189,6 +189,24 @@ describe('withTransaction utility', () => { }); }); + it('should clear the context after a transaction', async () => { + await appContext.run(async () => { + await withTransaction(async () => { + await model.save({ title: 'test-clear-session' }); + dbSessionContext.registerFileOperation({ + filename: 'test', + file: Readable.from(['content']), + type: 'document' as const, + }); + dbSessionContext.registerESIndexOperation([{}, 'select', 10]); + }); + + expect(dbSessionContext.getSession()).toBeUndefined(); + expect(dbSessionContext.getFileOperations()).toEqual([]); + expect(dbSessionContext.getReindexOperations()).toEqual([]); + }); + }); + describe('manual abort', () => { it('should allow manual abort without throwing error', async () => { await appContext.run(async () => { @@ -252,6 +270,7 @@ describe('withTransaction utility', () => { expect(sessionToTest?.hasEnded).toBe(true); }); }); + it('should do nothing when the feature flag is off', async () => { testingTenants.changeCurrentTenant({ featureFlags: { v1_transactions: false } }); @@ -266,7 +285,8 @@ describe('withTransaction utility', () => { }); }); }); - describe('Entities elasticsearch index', () => { + + describe('entities elasticsearch index', () => { beforeEach(async () => { await testingEnvironment.setUp( { @@ -349,6 +369,12 @@ describe('withTransaction utility', () => { }); describe('storeFile', () => { + afterAll(async () => { + await storage.removeFile('file_to_commit.txt', 'document'); + await storage.removeFile('file_to_fail.txt', 'document'); + await storage.removeFile('file_to_abort.txt', 'document'); + }); + it('should store file after transaction is committed', async () => { await appContext.run(async () => { await withTransaction(async () => { diff --git a/app/api/utils/withTransaction.ts b/app/api/utils/withTransaction.ts index 381b36094a..932afa6bdc 100644 --- a/app/api/utils/withTransaction.ts +++ b/app/api/utils/withTransaction.ts @@ -18,14 +18,13 @@ search.indexEntities = async (query, select, limit) => { const originalStoreFile = storage.storeFile.bind(storage); storage.storeFile = async (filename, file, type) => { - if (dbSessionContext.getSession() && !appContext.get('fileOperationsNow')) { + if (dbSessionContext.getSession()) { return dbSessionContext.registerFileOperation({ filename, file, type }); } return originalStoreFile(filename, file, type); }; const performDelayedFileStores = async () => { - appContext.set('fileOperationsNow', true); await storage.storeMultipleFiles(dbSessionContext.getFileOperations()); }; @@ -59,9 +58,9 @@ const withTransaction = async ( try { const result = await operation(context); if (!wasManuallyAborted) { + dbSessionContext.clearSession(); await performDelayedFileStores(); await session.commitTransaction(); - dbSessionContext.clearSession(); await performDelayedReindexes(); } return result; @@ -71,8 +70,8 @@ const withTransaction = async ( } throw e; } finally { - appContext.set('fileOperationsNow', false); dbSessionContext.clearSession(); + dbSessionContext.clearContext(); await session.endSession(); } };