diff --git a/app/api/entities.v2/EntityInputDataSchema.ts b/app/api/entities.v2/EntityInputDataSchema.ts new file mode 100644 index 0000000000..818c4296f8 --- /dev/null +++ b/app/api/entities.v2/EntityInputDataSchema.ts @@ -0,0 +1,88 @@ +import { availableLanguagesISO6391 } from 'shared/languagesList'; + +const linkSchema = { + type: 'object', + additionalProperties: false, + required: ['label', 'url'], + properties: { + label: { oneOf: [{ type: 'string' }, { type: 'null' }] }, + url: { oneOf: [{ type: 'string' }, { type: 'null' }] }, + }, +}; + +const dateRangeSchema = { + type: 'object', + additionalProperties: false, + required: ['from', 'to'], + properties: { + from: { oneOf: [{ type: 'number' }, { type: 'null' }] }, + to: { oneOf: [{ type: 'number' }, { type: 'null' }] }, + }, +}; + +const latLonSchema = { + type: 'object', + required: ['lon', 'lat'], + additionalProperties: false, + properties: { + label: { type: 'string' }, + lat: { type: 'number', minimum: -90, maximum: 90 }, + lon: { type: 'number', minimum: -180, maximum: 180 }, + }, +}; + +const geolocationSchema = { + type: 'array', + items: latLonSchema, +}; + +const propertyValueSchema = { + definitions: { linkSchema, dateRangeSchema, latLonSchema }, + oneOf: [ + { type: 'null' }, + { type: 'string' }, + { type: 'number' }, + { type: 'boolean' }, + linkSchema, + dateRangeSchema, + latLonSchema, + geolocationSchema, + ], +}; + +const metadataObjectSchema = { + type: 'object', + definitions: { propertyValueSchema }, + required: ['value'], + properties: { + value: propertyValueSchema, + }, +}; + +const metadataSchema = { + type: 'object', + definitions: { metadataObjectSchema }, + additionalProperties: { + anyOf: [{ type: 'array', items: metadataObjectSchema }], + }, + patternProperties: { + '^.*_nested$': { type: 'array', items: { type: 'object' } }, + }, +}; + +export const entityInputDataSchema = { + title: 'EntityInputData', + $schema: 'http://json-schema.org/schema#', + type: 'object', + required: ['_id', 'sharedId', 'language', 'title', 'template', 'metadata'], + properties: { + _id: { type: 'string' }, + sharedId: { type: 'string', minLength: 1 }, + language: { enum: availableLanguagesISO6391 }, + title: { type: 'string', minLength: 1 }, + template: { type: 'string' }, + metadata: metadataSchema, + }, +}; + +export const emitSchemaTypes = true; diff --git a/app/api/entities.v2/EntityInputDataType.d.ts b/app/api/entities.v2/EntityInputDataType.d.ts new file mode 100644 index 0000000000..beef2170b9 --- /dev/null +++ b/app/api/entities.v2/EntityInputDataType.d.ts @@ -0,0 +1,227 @@ +/* eslint-disable */ +/**AUTO-GENERATED. RUN yarn emit-types to update.*/ + +export interface EntityInputData { + _id: string; + sharedId: string; + language: + | 'ab' + | 'aa' + | 'af' + | 'ak' + | 'sq' + | 'am' + | 'ar' + | 'an' + | 'hy' + | 'as' + | 'av' + | 'ae' + | 'ay' + | 'az' + | 'bm' + | 'ba' + | 'eu' + | 'be' + | 'bn' + | 'bh' + | 'bi' + | 'bs' + | 'br' + | 'bg' + | 'my' + | 'ca' + | 'ch' + | 'ce' + | 'ny' + | 'zh' + | 'zh-Hans' + | 'zh-Hant' + | 'cv' + | 'kw' + | 'co' + | 'cr' + | 'hr' + | 'cs' + | 'da' + | 'dv' + | 'nl' + | 'dz' + | 'en' + | 'eo' + | 'et' + | 'ee' + | 'fo' + | 'fj' + | 'fi' + | 'fr' + | 'ff' + | 'gl' + | 'gd' + | 'gv' + | 'ka' + | 'de' + | 'el' + | 'gn' + | 'gu' + | 'ht' + | 'ha' + | 'he' + | 'hz' + | 'hi' + | 'ho' + | 'hu' + | 'is' + | 'io' + | 'ig' + | 'in' + | 'ia' + | 'ie' + | 'iu' + | 'ik' + | 'ga' + | 'it' + | 'ja' + | 'jv' + | 'kl' + | 'kn' + | 'kr' + | 'ks' + | 'kk' + | 'km' + | 'ki' + | 'rw' + | 'rn' + | 'ky' + | 'kv' + | 'kg' + | 'ko' + | 'ku' + | 'kj' + | 'lo' + | 'la' + | 'lv' + | 'li' + | 'ln' + | 'lt' + | 'lu' + | 'lg' + | 'lb' + | 'mk' + | 'mg' + | 'ms' + | 'ml' + | 'mt' + | 'mi' + | 'mr' + | 'mh' + | 'mn' + | 'na' + | 'nv' + | 'ng' + | 'nd' + | 'ne' + | 'no' + | 'nb' + | 'nn' + | 'oc' + | 'oj' + | 'cu' + | 'or' + | 'om' + | 'os' + | 'pi' + | 'ps' + | 'fa' + | 'pl' + | 'pt' + | 'pa' + | 'qu' + | 'rm' + | 'ro' + | 'ru' + | 'se' + | 'sm' + | 'sg' + | 'sa' + | 'sr' + | 'sh' + | 'st' + | 'tn' + | 'sn' + | 'ii' + | 'sd' + | 'si' + | 'ss' + | 'sk' + | 'sl' + | 'so' + | 'nr' + | 'es' + | 'su' + | 'sw' + | 'sv' + | 'tl' + | 'ty' + | 'tg' + | 'ta' + | 'tt' + | 'te' + | 'th' + | 'bo' + | 'ti' + | 'to' + | 'ts' + | 'tr' + | 'tk' + | 'tw' + | 'ug' + | 'uk' + | 'ur' + | 'uz' + | 've' + | 'vi' + | 'vo' + | 'wa' + | 'cy' + | 'wo' + | 'fy' + | 'xh' + | 'yi' + | 'yo' + | 'za' + | 'zu'; + title: string; + template: string; + metadata: { + [k: string]: + | { + value: + | null + | string + | number + | boolean + | { + label: string | null; + url: string | null; + } + | { + from: number | null; + to: number | null; + } + | { + label?: string; + lat: number; + lon: number; + } + | { + label?: string; + lat: number; + lon: number; + }[]; + [k: string]: unknown | undefined; + }[] + | undefined; + }; + [k: string]: unknown | undefined; +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/RequestEntityTranslation.ts b/app/api/externalIntegrations.v2/automaticTranslation/RequestEntityTranslation.ts new file mode 100644 index 0000000000..f0d25f9aef --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/RequestEntityTranslation.ts @@ -0,0 +1,107 @@ +import { getTenant } from 'api/common.v2/database/getConnectionForCurrentTenant'; +import { TaskManager } from 'api/services/tasksmanager/TaskManager'; +import { TemplatesDataSource } from 'api/templates.v2/contracts/TemplatesDataSource'; +import { EntityInputData } from 'api/entities.v2/EntityInputDataType'; +import { EntityInputValidator } from './contracts/EntityInputValidator'; +import { ATConfigService } from './services/GetAutomaticTranslationConfig'; +import { InvalidInputDataFormat } from './errors/generateATErrors'; + +export type ATTaskMessage = { + params: { + key: string[]; + text: string; + language_from: string; + languages_to: string[]; + }; +}; + +export class RequestEntityTranslation { + static SERVICE_NAME = 'AutomaticTranslation'; + + private taskManager: TaskManager; + + private templatesDS: TemplatesDataSource; + + private aTConfigService: ATConfigService; + + private inputValidator: EntityInputValidator; + + constructor( + taskManager: TaskManager, + templatesDS: TemplatesDataSource, + aTConfigService: ATConfigService, + inputValidator: EntityInputValidator + ) { + this.taskManager = taskManager; + this.templatesDS = templatesDS; + this.aTConfigService = aTConfigService; + this.inputValidator = inputValidator; + } + + // eslint-disable-next-line max-statements + async execute(entity: EntityInputData | unknown) { + if (!this.inputValidator.validate(entity)) { + throw new InvalidInputDataFormat(this.inputValidator.getErrors()[0]); + } + const atConfig = await this.aTConfigService.get(); + const atTemplateConfig = atConfig.templates.find( + t => t.template === entity.template?.toString() + ); + + if (!atTemplateConfig) { + return; + } + + const template = await this.templatesDS.getById(atTemplateConfig?.template); + + const languageFrom = entity.language; + if (!atConfig.languages.includes(languageFrom)) { + return; + } + + const languagesTo = atConfig.languages.filter(language => language !== languageFrom); + atTemplateConfig?.commonProperties.forEach(async commonPropId => { + const commonPropName = template?.commonProperties.find( + prop => prop.id === commonPropId + )?.name; + + if (!commonPropName) { + throw new Error('Common property not found'); + } + + if (!(typeof entity[commonPropName] === 'string')) { + throw new Error('Common property is not a string'); + } + + await this.taskManager.startTask({ + params: { + key: [getTenant().name, entity.sharedId, commonPropName], + text: entity[commonPropName], + language_from: languageFrom, + languages_to: languagesTo, + }, + }); + }); + atTemplateConfig?.properties.forEach(async propId => { + const propName = template?.properties.find(prop => prop.id === propId)?.name; + if (!propName) { + throw new Error('Property not found'); + } + + if (!(typeof entity.metadata[propName]?.[0].value === 'string')) { + throw new Error('Property is not a string'); + } + + if (entity.metadata[propName]?.[0].value) { + await this.taskManager.startTask({ + params: { + key: [getTenant().name, entity.sharedId, propName], + text: entity.metadata[propName][0].value, + language_from: languageFrom, + languages_to: languagesTo, + }, + }); + } + }); + } +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/contracts/EntityInputValidator.ts b/app/api/externalIntegrations.v2/automaticTranslation/contracts/EntityInputValidator.ts new file mode 100644 index 0000000000..91692e571c --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/contracts/EntityInputValidator.ts @@ -0,0 +1,6 @@ +import { EntityInputData } from 'api/entities.v2/EntityInputDataType'; + +export interface EntityInputValidator { + getErrors(): string[]; + validate(data: unknown): data is EntityInputData; +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/EntityInputValidator.ts b/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/EntityInputValidator.ts new file mode 100644 index 0000000000..f0deedbfcb --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/infrastructure/EntityInputValidator.ts @@ -0,0 +1,20 @@ +import { Ajv } from 'ajv'; +import { entityInputDataSchema } from 'api/entities.v2/EntityInputDataSchema'; +import { EntityInputData } from 'api/entities.v2/EntityInputDataType'; +import { EntityInputValidator } from '../contracts/EntityInputValidator'; + +export class AJVEntityInputValidator implements EntityInputValidator { + private errors: string[] = []; + + getErrors() { + return this.errors; + } + + validate(data: unknown) { + const ajv = new Ajv({ strict: false }); + const validate = ajv.compile(entityInputDataSchema); + const result = validate(data); + this.errors = validate.errors ? validate.errors?.map(e => JSON.stringify(e.params)) : []; + return result; + } +} diff --git a/app/api/externalIntegrations.v2/automaticTranslation/services/GetAutomaticTranslationConfig.ts b/app/api/externalIntegrations.v2/automaticTranslation/services/GetAutomaticTranslationConfig.ts index c5caaa1437..dec59ce500 100644 --- a/app/api/externalIntegrations.v2/automaticTranslation/services/GetAutomaticTranslationConfig.ts +++ b/app/api/externalIntegrations.v2/automaticTranslation/services/GetAutomaticTranslationConfig.ts @@ -3,7 +3,7 @@ import { TemplatesDataSource } from 'api/templates.v2/contracts/TemplatesDataSou import { ATGateway } from '../contracts/ATGateway'; import { ATConfigDataSource } from '../contracts/ATConfigDataSource'; -export class GetAutomaticTranslationConfig { +export class ATConfigService { private settings: SettingsDataSource; private config: ATConfigDataSource; @@ -24,7 +24,7 @@ export class GetAutomaticTranslationConfig { this.automaticTranslation = automaticTranslation; } - async execute() { + async get() { const config = await this.config.get(); const validProperties = await this.templates.getAllTextProperties().all(); diff --git a/app/api/externalIntegrations.v2/automaticTranslation/services/specs/GetAutomaticTranslationConfig.spec.ts b/app/api/externalIntegrations.v2/automaticTranslation/services/specs/GetAutomaticTranslationConfig.spec.ts index 2181008369..b622a0ff8d 100644 --- a/app/api/externalIntegrations.v2/automaticTranslation/services/specs/GetAutomaticTranslationConfig.spec.ts +++ b/app/api/externalIntegrations.v2/automaticTranslation/services/specs/GetAutomaticTranslationConfig.spec.ts @@ -6,11 +6,11 @@ import { getFixturesFactory } from 'api/utils/fixturesFactory'; import { testingEnvironment } from 'api/utils/testingEnvironment'; import { MongoATConfigDataSource } from '../../infrastructure/MongoATConfigDataSource'; import { ATExternalAPI } from '../../infrastructure/ATExternalAPI'; -import { GetAutomaticTranslationConfig } from '../GetAutomaticTranslationConfig'; +import { ATConfigService } from '../GetAutomaticTranslationConfig'; const createService = () => { const transactionManager = DefaultTransactionManager(); - return new GetAutomaticTranslationConfig( + return new ATConfigService( DefaultSettingsDataSource(transactionManager), new MongoATConfigDataSource(getConnection(), transactionManager), DefaultTemplatesDataSource(transactionManager), @@ -89,7 +89,7 @@ afterAll(async () => { describe('GetAutomaticTranslationConfig', () => { it('should return only title, text and markdown properties', async () => { - const config = await createService().execute(); + const config = await createService().get(); expect(config.templates[0]).toEqual({ template: fixtures.id('template 1').toString(), commonProperties: [fixtures.commonPropertiesTitleId('template 1')], @@ -98,7 +98,7 @@ describe('GetAutomaticTranslationConfig', () => { }); it('should not include properties that no longer exist', async () => { - const config = await createService().execute(); + const config = await createService().get(); expect(config.templates[0].properties).toEqual([ fixtures.id('text property').toString(), fixtures.id('rich text').toString(), @@ -106,7 +106,7 @@ describe('GetAutomaticTranslationConfig', () => { }); it('should not include properties belonging to other templates', async () => { - const config = await createService().execute(); + const config = await createService().get(); expect(config.templates[1]).toEqual({ template: fixtures.id('template 2').toString(), commonProperties: [], @@ -115,12 +115,12 @@ describe('GetAutomaticTranslationConfig', () => { }); it('should return languages available filtered by the supported languages of automatic translation', async () => { - const config = await createService().execute(); + const config = await createService().get(); expect(config.languages).toEqual(['en', 'es']); }); it('should allow configuring only title without any properties', async () => { - const config = await createService().execute(); + const config = await createService().get(); expect(config.templates[2]).toEqual({ template: fixtures.id('template 3').toString(), commonProperties: [fixtures.commonPropertiesTitleId('template 3')], @@ -129,7 +129,7 @@ describe('GetAutomaticTranslationConfig', () => { }); it('should not include properties configurations belonging to an unexistent template', async () => { - const config = await createService().execute(); + const config = await createService().get(); expect(config.templates[3]).toBeUndefined(); }); }); diff --git a/app/api/externalIntegrations.v2/automaticTranslation/specs/RequestEntityTranslation.spec.ts b/app/api/externalIntegrations.v2/automaticTranslation/specs/RequestEntityTranslation.spec.ts new file mode 100644 index 0000000000..2b762cb80d --- /dev/null +++ b/app/api/externalIntegrations.v2/automaticTranslation/specs/RequestEntityTranslation.spec.ts @@ -0,0 +1,144 @@ +import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults'; +import { + ATConfig, + ATTemplateConfig, +} from 'api/externalIntegrations.v2/automaticTranslation/model/ATConfig'; +import { TaskManager } from 'api/services/tasksmanager/TaskManager'; +import { DefaultTemplatesDataSource } from 'api/templates.v2/database/data_source_defaults'; +import { getFixturesFactory } from 'api/utils/fixturesFactory'; +import { testingEnvironment } from 'api/utils/testingEnvironment'; +import { LanguageISO6391 } from 'shared/types/commonTypes'; +import { EntitySchema } from 'shared/types/entityType'; +import { ATTaskMessage, RequestEntityTranslation } from '../RequestEntityTranslation'; +import { ATConfigService } from '../services/GetAutomaticTranslationConfig'; +import { AJVEntityInputValidator } from '../infrastructure/EntityInputValidator'; +import { InvalidInputDataFormat } from '../errors/generateATErrors'; + +const factory = getFixturesFactory(); +const fixtures = { + templates: [ + factory.template('template1', [factory.property('text1'), factory.property('empty_text')]), + ], + entities: [ + ...factory.entityInMultipleLanguages(['en', 'es'], 'entity1', 'template1', { + text1: [{ value: 'original text1' }], + empty_text: [{ value: '' }], + }), + ], + settings: [ + { + languages: [ + { label: 'en', key: 'en' as LanguageISO6391, default: true }, + { label: 'es', key: 'es' as LanguageISO6391 }, + ], + }, + ], +}; + +// @ts-ignore +class TestATConfigService implements ATConfigService { + // eslint-disable-next-line class-methods-use-this + async get() { + return new ATConfig( + true, + ['es', 'en'], + [ + new ATTemplateConfig( + factory.idString('template1'), + [factory.idString('text1'), factory.idString('empty_text')], + [factory.commonPropertiesTitleId('template1')] + ), + ] + ); + } +} + +let taskManager: TaskManager; + +beforeEach(async () => { + await testingEnvironment.setUp(fixtures); + await testingEnvironment.setTenant('tenant'); + taskManager = new TaskManager({ + serviceName: RequestEntityTranslation.SERVICE_NAME, + }); + jest.spyOn(taskManager, 'startTask').mockImplementation(async () => ''); +}); + +afterAll(async () => { + await testingEnvironment.tearDown(); +}); + +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(); + + const requestEntityTranslation = new RequestEntityTranslation( + taskManager, + DefaultTemplatesDataSource(DefaultTransactionManager()), + // @ts-ignore + new TestATConfigService(), + new AJVEntityInputValidator() + ); + + await requestEntityTranslation.execute(languageFromEntity!); + + expect(taskManager.startTask).toHaveBeenCalledTimes(2); + + expect(taskManager.startTask).toHaveBeenCalledWith({ + params: { + key: ['tenant', 'entity1', 'title'], + text: 'entity1', + language_from: 'en', + languages_to: ['es'], + }, + }); + + expect(taskManager.startTask).toHaveBeenCalledWith({ + params: { + key: ['tenant', 'entity1', 'text1'], + text: 'original text1', + language_from: 'en', + languages_to: ['es'], + }, + }); + }); + + it('should do nothing if entity.language is not supported', async () => { + const entityWithNotSupportedLanguage = factory.entity( + 'entity2', + 'template1', + {}, + { language: 'pt' } + ); + entityWithNotSupportedLanguage._id = entityWithNotSupportedLanguage?._id?.toString(); + entityWithNotSupportedLanguage.template = entityWithNotSupportedLanguage?.template?.toString(); + + const requestEntityTranslation = new RequestEntityTranslation( + taskManager, + DefaultTemplatesDataSource(DefaultTransactionManager()), + // @ts-ignore + new TestATConfigService(), + new AJVEntityInputValidator() + ); + + await requestEntityTranslation.execute(entityWithNotSupportedLanguage); + expect(taskManager.startTask).not.toHaveBeenCalled(); + }); + + it('should validate input has proper shape at runtime', async () => { + const requestEntityTranslation = new RequestEntityTranslation( + taskManager, + DefaultTemplatesDataSource(DefaultTransactionManager()), + // @ts-ignore + new TestATConfigService(), + new AJVEntityInputValidator() + ); + + const invalidEntity = { invalid_prop: true }; + await expect(requestEntityTranslation.execute(invalidEntity)).rejects.toEqual( + new InvalidInputDataFormat('{"missingProperty":"_id"}') + ); + }); +}); diff --git a/app/api/services/informationextraction/InformationExtraction.ts b/app/api/services/informationextraction/InformationExtraction.ts index 97d2d654b2..3f430689a6 100644 --- a/app/api/services/informationextraction/InformationExtraction.ts +++ b/app/api/services/informationextraction/InformationExtraction.ts @@ -7,7 +7,7 @@ import _ from 'lodash'; import { ObjectId } from 'mongodb'; import { storage } from 'api/files'; -import { ResultsMessage, TaskManager } from 'api/services/tasksmanager/TaskManager'; +import { TaskManager } from 'api/services/tasksmanager/TaskManager'; import { IXSuggestionsModel } from 'api/suggestions/IXSuggestionsModel'; import { SegmentationModel } from 'api/services/pdfsegmentation/segmentationModel'; import { EnforcedWithId } from 'api/odm'; @@ -57,15 +57,33 @@ interface TaskParameters { options?: { label: string; id: string }[]; } +interface TaskMessage { + tenant: string; + task: TaskTypes; + params?: TaskParameters; +} + type ResultParameters = TaskParameters; +/* eslint-disable camelcase */ +interface ResultMessage

{ + tenant: string; + task: TaskTypes; + params?: P; + data_url?: string; + file_url?: string; + success?: boolean; + error_message?: string; +} +/* eslint-enable camelcase */ + interface InternalResultParameters { id: ObjectId; } -type IXResultsMessage = ResultsMessage; +type IXResultsMessage = ResultMessage; -type InternalIXResultsMessage = ResultsMessage; +type InternalIXResultsMessage = ResultMessage; interface CommonMaterialsData { xml_file_name: string; @@ -109,7 +127,7 @@ async function fetchCandidates(property: PropertySchema) { class InformationExtraction { static SERVICE_NAME = 'information_extraction'; - public taskManager: TaskManager; + public taskManager: TaskManager; static mock: any; @@ -149,9 +167,9 @@ class InformationExtraction { file: FileWithAggregation, _data: CommonMaterialsData ): MaterialsData => { - const language_iso = languages.get(file.language!, 'ISO639_1') || defaultTrainingLanguage; + const languageIso = languages.get(file.language!, 'ISO639_1') || defaultTrainingLanguage; - let data: MaterialsData = { ..._data, language_iso }; + let data: MaterialsData = { ..._data, language_iso: languageIso }; const noExtractedData = propertyTypeIsWithoutExtractedMetadata(propertyType); diff --git a/app/api/services/tasksmanager/TaskManager.ts b/app/api/services/tasksmanager/TaskManager.ts index f9c57b9254..e2410894c2 100644 --- a/app/api/services/tasksmanager/TaskManager.ts +++ b/app/api/services/tasksmanager/TaskManager.ts @@ -9,17 +9,17 @@ type DefaultTaskType = string; type DefaultMessageParameters = Record; -export type TaskMessage = { +export type TaskMessage = { tenant: string; - task: T; - params?: P; + task: DefaultTaskType; + params?: DefaultMessageParameters; }; /* eslint-disable camelcase */ -export interface ResultsMessage { +export interface ResultsMessage { tenant: string; - task: T; - params?: P; + task: DefaultTaskType; + params?: DefaultMessageParameters; data_url?: string; file_url?: string; success?: boolean; @@ -27,20 +27,16 @@ export interface ResultsMessage { +export interface Service { serviceName: string; - processResults?: (results: ResultsMessage) => Promise; + processResults?: (results: R) => Promise; processResultsMessageHiddenTime?: number; } -export class TaskManager< - T = DefaultTaskType, - S = DefaultMessageParameters, - R = DefaultMessageParameters, -> { +export class TaskManager { redisSMQ: RedisSMQ; - readonly service: Service; + readonly service: Service; readonly taskQueue: string; @@ -50,7 +46,7 @@ export class TaskManager< redisClient: RedisClient; - constructor(service: Service) { + constructor(service: Service) { this.service = service; this.taskQueue = `${service.serviceName}_tasks`; this.resultsQueue = `${service.serviceName}_results`; @@ -121,7 +117,7 @@ export class TaskManager< } } - async startTask(taskMessage: TaskMessage) { + async startTask(taskMessage: T) { if (!this.redisClient.connected) { throw new Error('Redis is not connected'); } @@ -133,7 +129,9 @@ export class TaskManager< } async stop() { - await this.repeater!.stop(); + if (this.repeater) { + await this.repeater.stop(); + } await this.redisClient.end(true); } } diff --git a/app/api/utils/testingEnvironment.ts b/app/api/utils/testingEnvironment.ts index 0368e697eb..3a61d20077 100644 --- a/app/api/utils/testingEnvironment.ts +++ b/app/api/utils/testingEnvironment.ts @@ -17,10 +17,10 @@ const testingEnvironment = { await this.setElastic(elasticIndex); }, - async setTenant(name = 'defaultDB') { + async setTenant(name?: string) { testingTenants.mockCurrentTenant({ - name: testingDB.dbName || name, - dbName: testingDB.dbName || name, + name: name || testingDB.dbName || 'defaultDB', + dbName: testingDB.dbName || name || 'defaultDB', indexName: 'index', }); await setupTestUploadedPaths(); diff --git a/app/shared/languagesList.ts b/app/shared/languagesList.ts index 023968f977..8bdbac915c 100644 --- a/app/shared/languagesList.ts +++ b/app/shared/languagesList.ts @@ -1359,4 +1359,6 @@ const language = (key: string, purpose: keyof (typeof elasticLanguages)[number] return elasticLanguages[key] ? elasticLanguages[key][purpose] : defaultValue; }; -export { elasticLanguages, availableLanguages, language }; +const availableLanguagesISO6391 = availableLanguages.map(l => l.key); + +export { elasticLanguages, availableLanguages, language, availableLanguagesISO6391 };