diff --git a/app/api/common.v2/database/MongoResultSet.ts b/app/api/common.v2/database/MongoResultSet.ts index cd19ce32b7..4d44da5281 100644 --- a/app/api/common.v2/database/MongoResultSet.ts +++ b/app/api/common.v2/database/MongoResultSet.ts @@ -1,7 +1,7 @@ /* eslint-disable no-await-in-loop */ import { AggregationCursor, FindCursor } from 'mongodb'; -import { BreakLoopSignal, ResultSet } from '../contracts/ResultSet'; import { objectIndex } from 'shared/data_utils/objectIndex'; +import { BreakLoopSignal, ResultSet } from '../contracts/ResultSet'; interface MapperFunc { (elem: T): U | Promise; diff --git a/app/api/externalIntegrations.v2/automaticTranslation/AutomaticTranslationFactory.ts b/app/api/externalIntegrations.v2/automaticTranslation/AutomaticTranslationFactory.ts index 94337c65b8..31c2f1d632 100644 --- a/app/api/externalIntegrations.v2/automaticTranslation/AutomaticTranslationFactory.ts +++ b/app/api/externalIntegrations.v2/automaticTranslation/AutomaticTranslationFactory.ts @@ -53,11 +53,13 @@ const AutomaticTranslationFactory = { }, defaultRequestEntityTranslation() { + const transactionManager = DefaultTransactionManager(); return new RequestEntityTranslation( new TaskManager({ serviceName: RequestEntityTranslation.SERVICE_NAME, }), - AutomaticTranslationFactory.defaultATConfigDataSource(DefaultTransactionManager()), + AutomaticTranslationFactory.defaultATConfigDataSource(transactionManager), + DefaultEntitiesDataSource(transactionManager), new Validator(entityInputDataSchema), DefaultLogger() ); diff --git a/app/api/externalIntegrations.v2/automaticTranslation/RequestEntityTranslation.ts b/app/api/externalIntegrations.v2/automaticTranslation/RequestEntityTranslation.ts index 0d7a3fe58a..62f04ffbdd 100644 --- a/app/api/externalIntegrations.v2/automaticTranslation/RequestEntityTranslation.ts +++ b/app/api/externalIntegrations.v2/automaticTranslation/RequestEntityTranslation.ts @@ -3,6 +3,8 @@ import { Entity } from 'api/entities.v2/model/Entity'; import { EntityInputModel } from 'api/entities.v2/types/EntityInputDataType'; import { Logger } from 'api/log.v2/contracts/Logger'; import { TaskManager } from 'api/services/tasksmanager/TaskManager'; +import { EntitiesDataSource } from 'api/entities.v2/contracts/EntitiesDataSource'; +import { LanguageISO6391 } from 'shared/types/commonTypes'; import { ATConfigDataSource } from './contracts/ATConfigDataSource'; import { Validator } from './infrastructure/Validator'; @@ -16,22 +18,29 @@ export type ATTaskMessage = { export class RequestEntityTranslation { static SERVICE_NAME = 'translations'; - private logger: Logger; + static AITranslationPendingText = '(AI translation pending)'; private taskManager: TaskManager; private ATConfigDS: ATConfigDataSource; + private entitiesDS: EntitiesDataSource; + private inputValidator: Validator; + private logger: Logger; + + // eslint-disable-next-line max-params constructor( taskManager: TaskManager, ATConfigDS: ATConfigDataSource, + entitiesDS: EntitiesDataSource, inputValidator: Validator, logger: Logger ) { this.taskManager = taskManager; this.ATConfigDS = ATConfigDS; + this.entitiesDS = entitiesDS; this.inputValidator = inputValidator; this.logger = logger; } @@ -58,10 +67,25 @@ export class RequestEntityTranslation { const entity = Entity.fromInputModel(entityInputModel); - atTemplateConfig?.properties.forEach(async property => { + await atTemplateConfig?.properties.reduce(async (prev, property) => { + await prev; const propertyValue = entity.getPropertyValue(property); if (propertyValue) { + const entities = this.entitiesDS.getByIds([entity.sharedId]); + const pendingText = `${RequestEntityTranslation.AITranslationPendingText} ${propertyValue}`; + + await entities.forEach(async fetchedEntity => { + if (languagesTo.includes(fetchedEntity.language as LanguageISO6391)) { + await this.entitiesDS.updateEntity( + fetchedEntity.changePropertyValue(property, pendingText) + ); + this.logger.info( + `[AT] - Pending translation saved on DB - ${property.name}: ${pendingText}` + ); + } + }); + await this.taskManager.startTask({ key: [getTenant().name, entity.sharedId, property.id], text: propertyValue, @@ -78,6 +102,6 @@ export class RequestEntityTranslation { })}` ); } - }); + }, Promise.resolve()); } } diff --git a/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts b/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts index e83d5f3361..5876c6e566 100644 --- a/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts +++ b/app/api/externalIntegrations.v2/automaticTranslation/SaveEntityTranslations.ts @@ -57,7 +57,6 @@ export class SaveEntityTranslations { }); } - // eslint-disable-next-line max-statements private async getProperty(entitySharedId: string, propertyId: string) { const entity = await this.entitiesDS.getByIds([entitySharedId]).first(); if (!entity) { diff --git a/app/api/externalIntegrations.v2/automaticTranslation/adapters/driving/specs/ATEntityCreatedListener.spec.ts b/app/api/externalIntegrations.v2/automaticTranslation/adapters/driving/specs/ATEntityCreationListener.spec.ts similarity index 100% rename from app/api/externalIntegrations.v2/automaticTranslation/adapters/driving/specs/ATEntityCreatedListener.spec.ts rename to app/api/externalIntegrations.v2/automaticTranslation/adapters/driving/specs/ATEntityCreationListener.spec.ts diff --git a/app/api/externalIntegrations.v2/automaticTranslation/specs/RequestEntityTranslation.spec.ts b/app/api/externalIntegrations.v2/automaticTranslation/specs/RequestEntityTranslation.spec.ts index da6e200e55..263112c455 100644 --- a/app/api/externalIntegrations.v2/automaticTranslation/specs/RequestEntityTranslation.spec.ts +++ b/app/api/externalIntegrations.v2/automaticTranslation/specs/RequestEntityTranslation.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable max-classes-per-file */ import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; import { entityInputDataSchema } from 'api/entities.v2/types/EntityInputDataSchema'; import { EntityInputModel } from 'api/entities.v2/types/EntityInputDataType'; @@ -5,10 +6,11 @@ import { Logger } from 'api/log.v2/contracts/Logger'; import { createMockLogger } from 'api/log.v2/infrastructure/MockLogger'; import { TaskManager } from 'api/services/tasksmanager/TaskManager'; import { getFixturesFactory } from 'api/utils/fixturesFactory'; -import { DBFixture } from 'api/utils/testing_db'; +import testingDB, { DBFixture } from 'api/utils/testing_db'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import { LanguageISO6391 } from 'shared/types/commonTypes'; import { EntitySchema } from 'shared/types/entityType'; +import { DefaultEntitiesDataSource } from 'api/entities.v2/database/data_source_defaults'; import { AutomaticTranslationFactory } from '../AutomaticTranslationFactory'; import { ValidationError, Validator } from '../infrastructure/Validator'; import { ATTaskMessage, RequestEntityTranslation } from '../RequestEntityTranslation'; @@ -19,7 +21,7 @@ const fixtures: DBFixture = { factory.template('template1', [factory.property('text1'), factory.property('empty_text')]), ], entities: [ - ...factory.entityInMultipleLanguages(['en', 'es'], 'entity1', 'template1', { + ...factory.entityInMultipleLanguages(['en', 'es', 'pt'], 'entity1', 'template1', { text1: [{ value: 'original text1' }], empty_text: [{ value: '' }], }), @@ -29,6 +31,7 @@ const fixtures: DBFixture = { languages: [ { label: 'en', key: 'en' as LanguageISO6391, default: true }, { label: 'es', key: 'es' as LanguageISO6391 }, + { label: 'pt', key: 'pt' as LanguageISO6391 }, ], features: { automaticTranslation: { @@ -58,9 +61,13 @@ beforeEach(async () => { serviceName: RequestEntityTranslation.SERVICE_NAME, }); jest.spyOn(taskManager, 'startTask').mockImplementation(async () => ''); + + const transactionManager = DefaultTransactionManager(); + requestEntityTranslation = new RequestEntityTranslation( taskManager, - AutomaticTranslationFactory.defaultATConfigDataSource(DefaultTransactionManager()), + AutomaticTranslationFactory.defaultATConfigDataSource(transactionManager), + DefaultEntitiesDataSource(transactionManager), new Validator(entityInputDataSchema), mockLogger ); @@ -71,27 +78,55 @@ afterAll(async () => { }); describe('RequestEntityTranslation', () => { - it('should send a task in the automatic translation service queue', async () => { - const languageFromEntity = fixtures.entities?.find(e => e.language === 'en') as EntitySchema; - languageFromEntity._id = languageFromEntity?._id?.toString(); - languageFromEntity.template = languageFromEntity?.template?.toString(); - - await requestEntityTranslation.execute(languageFromEntity!); - - expect(taskManager.startTask).toHaveBeenCalledTimes(2); + describe('on requests that should be processed', () => { + beforeEach(async () => { + const languageFromEntity = { + ...fixtures.entities?.find(e => e.language === 'en'), + } as EntitySchema; + languageFromEntity._id = languageFromEntity?._id?.toString(); + languageFromEntity.template = languageFromEntity?.template?.toString(); + + await requestEntityTranslation.execute(languageFromEntity!); + }); - expect(taskManager.startTask).toHaveBeenCalledWith({ - key: ['tenant', 'entity1', factory.commonPropertiesTitleId('template1').toString()], - text: 'entity1', - language_from: 'en', - languages_to: ['es'], + it('should call save entities with pending translation', async () => { + const entities = + (await testingDB.mongodb?.collection('entities').find({ sharedId: 'entity1' }).toArray()) || + []; + expect(entities.find(e => e.language === 'es')).toMatchObject({ + title: `${RequestEntityTranslation.AITranslationPendingText} entity1`, + metadata: { + text1: [{ value: `${RequestEntityTranslation.AITranslationPendingText} original text1` }], + }, + }); + expect(entities.find(e => e.language === 'pt')).toMatchObject({ + title: `${RequestEntityTranslation.AITranslationPendingText} entity1`, + metadata: { + text1: [{ value: `${RequestEntityTranslation.AITranslationPendingText} original text1` }], + }, + }); + expect(entities.find(e => e.language === 'en')).toMatchObject({ + title: 'entity1', + metadata: { text1: [{ value: 'original text1' }] }, + }); }); - expect(taskManager.startTask).toHaveBeenCalledWith({ - key: ['tenant', 'entity1', factory.property('text1')._id?.toString()], - text: 'original text1', - language_from: 'en', - languages_to: ['es'], + it('should send a task to the automatic translation service queue', () => { + expect(taskManager.startTask).toHaveBeenCalledTimes(2); + + expect(taskManager.startTask).toHaveBeenCalledWith({ + key: ['tenant', 'entity1', factory.commonPropertiesTitleId('template1').toString()], + text: 'entity1', + language_from: 'en', + languages_to: ['es', 'pt'], + }); + + expect(taskManager.startTask).toHaveBeenCalledWith({ + key: ['tenant', 'entity1', factory.property('text1')._id?.toString()], + text: 'original text1', + language_from: 'en', + languages_to: ['es', 'pt'], + }); }); }); @@ -100,7 +135,7 @@ describe('RequestEntityTranslation', () => { 'entity2', 'template1', {}, - { language: 'pt' } + { language: 'kg' } ); entityWithNotSupportedLanguage._id = entityWithNotSupportedLanguage?._id?.toString(); entityWithNotSupportedLanguage.template = entityWithNotSupportedLanguage?.template?.toString(); @@ -116,16 +151,6 @@ describe('RequestEntityTranslation', () => { ); }); - it('should call Logger.info two times', async () => { - const languageFromEntity = fixtures.entities?.find(e => e.language === 'en') as EntitySchema; - languageFromEntity._id = languageFromEntity?._id?.toString(); - languageFromEntity.template = languageFromEntity?.template?.toString(); - - await requestEntityTranslation.execute(languageFromEntity!); - - expect(mockLogger.info).toHaveBeenCalledTimes(2); - }); - it('should NOT send any task if there is no other language to translate', async () => { await testingEnvironment.setFixtures({ ...fixtures, diff --git a/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts b/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts index 4febc3379a..d2142b1514 100644 --- a/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts +++ b/app/api/externalIntegrations.v2/automaticTranslation/specs/SaveEntityTranslations.spec.ts @@ -10,36 +10,12 @@ import { Logger } from 'api/log.v2/contracts/Logger'; import { SaveEntityTranslations } from '../SaveEntityTranslations'; import { TranslationResult, translationResultSchema } from '../types/TranslationResult'; import { ValidationError, Validator } from '../infrastructure/Validator'; +import { saveEntityFixtures } from './fixtures/SaveEntity.fixtures'; const factory = getFixturesFactory(); beforeEach(async () => { - const fixtures = { - templates: [ - factory.template('template1', [ - { - _id: factory.id('propertyName'), - name: 'propertyName', - type: 'text', - label: 'Prop 1', - }, - ]), - ], - entities: [ - ...factory.entityInMultipleLanguages(['en', 'pt', 'es'], 'entity', 'template1', { - propertyName: [{ value: 'original text' }], - }), - ], - settings: [ - { - languages: [ - { label: 'en', key: 'en' as LanguageISO6391, default: true }, - { label: 'pt', key: 'pt' as LanguageISO6391 }, - { label: 'es', key: 'es' as LanguageISO6391 }, - ], - }, - ], - }; + const fixtures = saveEntityFixtures(factory); await testingEnvironment.setUp(fixtures); }); @@ -47,7 +23,7 @@ afterAll(async () => { await testingEnvironment.tearDown(); }); -describe('GenerateAutomaticTranslationConfig', () => { +describe('SaveEntityTranslations', () => { let saveEntityTranslations: SaveEntityTranslations; let mockLogger: Logger; diff --git a/app/api/externalIntegrations.v2/automaticTranslation/specs/fixtures/SaveEntity.fixtures.ts b/app/api/externalIntegrations.v2/automaticTranslation/specs/fixtures/SaveEntity.fixtures.ts new file mode 100644 index 0000000000..abd75c15a2 --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/specs/fixtures/SaveEntity.fixtures.ts @@ -0,0 +1,37 @@ +import { getFixturesFactory } from 'api/utils/fixturesFactory'; +import { LanguageISO6391 } from 'shared/types/commonTypes'; + +export const saveEntityFixtures = (factory: ReturnType) => ({ + templates: [ + factory.template('template1', [ + { + _id: factory.id('propertyName'), + name: 'propertyName', + type: 'text', + label: 'Prop 1', + }, + ]), + ], + entities: [ + ...factory.entityInMultipleLanguages(['en', 'pt', 'es'], 'entity', 'template1', { + propertyName: [{ value: 'original text' }], + }), + ...factory.entityInMultipleLanguages( + ['en', 'pt', 'es'], + 'entity_with_wrong_template', + 'wrong_template', + { + propertyName: [{ value: 'original text' }], + } + ), + ], + settings: [ + { + languages: [ + { label: 'en', key: 'en' as LanguageISO6391, default: true }, + { label: 'pt', key: 'pt' as LanguageISO6391 }, + { label: 'es', key: 'es' as LanguageISO6391 }, + ], + }, + ], +}); diff --git a/app/api/utils/fixturesFactory.ts b/app/api/utils/fixturesFactory.ts index e6c2c3e4d3..a9495886c2 100644 --- a/app/api/utils/fixturesFactory.ts +++ b/app/api/utils/fixturesFactory.ts @@ -121,6 +121,7 @@ function getFixturesFactory() { }; }, + // eslint-disable-next-line max-params entityInMultipleLanguages( languages: string[], id: string,