Skip to content

Commit

Permalink
Automatic translation config schema (#7112)
Browse files Browse the repository at this point in the history
* Automatic translation config schema

* WIP, GetAutomaticTranslationConfig

* Wip, GetAutomaticTranslationConfig service

* Filter out wrong configs

- Filter out configs belonging to non existent templates
- Filter out properties belonging to other templates

* GetAutomaticTranslation filters languages.

Filters configured languages by the languages supported by the external
service

* Typo and naming fix

* Moved service to externalIntegrations folder

* Fixed ESLint suggestion

* Create Models for the service and refined tests

* Fixed Typo

* Moved logic to model and updated type definitions

* Implemented getByNames method.

* Prepared script for Generating AT Config from Semantic data

* commonProperties on the Template model

* Fix Test

* Remove AT config from settingsDS

moved responsability to the new AutomaticTranslationConfigDS

* WIP Red test to implement invalid config test case

* AutomaticTranslationConfigDataSource fix

now returns and accepts domain object instead of a db raw type

* GenerateAutomaticTranslationConfig use case

* generateAutomaticTranslation cli script

- scripts.v2 included in tsconfig

* tenants tearDown, closes mongo model change stream

* Fixed type errors

* WIP, input data shape validation

* Renamed to AT most of the internal module files.

* Moved validator to own contract

* Changed Validator to return array of strings

* Return all error params.

* Implemented typeguard for validator

* Fixed prettier desync with ESLint

* Fixed eslint errors

* Removed prettier exceptions

* Removed extra eslintrc rule.

---------

Co-authored-by: RafaPolit <rafaelpolit@gmail.com>
  • Loading branch information
daneryl and RafaPolit authored Aug 13, 2024
1 parent c2fb6e8 commit fa6727a
Show file tree
Hide file tree
Showing 32 changed files with 805 additions and 38 deletions.
4 changes: 2 additions & 2 deletions app/api/entities.v2/database/MongoEntitiesDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { Entity, MetadataValue } from '../model/Entity';

export class MongoEntitiesDataSource
extends MongoDataSource<EntityDBO>
// eslint-disable-next-line prettier/prettier
implements EntitiesDataSource {
implements EntitiesDataSource
{
protected collectionName = 'entities';

private settingsDS: MongoSettingsDataSource;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DefaultTransactionManager } from 'api/common.v2/database/data_source_defaults';
import { getConnection } from 'api/common.v2/database/getConnectionForCurrentTenant';
import { MongoTemplatesDataSource } from 'api/templates.v2/database/MongoTemplatesDataSource';
import { GenerateAutomaticTranslationsCofig } from './GenerateAutomaticTranslationConfig';
import { MongoATConfigDataSource } from './infrastructure/MongoATConfigDataSource';
import { AJVATConfigValidator } from './infrastructure/AJVATConfigValidator';

const AutomaticTranslationFactory = {
defaultGenerateATConfig() {
return new GenerateAutomaticTranslationsCofig(
new MongoATConfigDataSource(getConnection(), DefaultTransactionManager()),
new MongoTemplatesDataSource(getConnection(), DefaultTransactionManager()),
new AJVATConfigValidator()
);
},
};

export { AutomaticTranslationFactory };
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { TemplatesDataSource } from 'api/templates.v2/contracts/TemplatesDataSource';
import { ATConfigDataSource } from './contracts/ATConfigDataSource';
import { ATTemplateConfig } from './model/ATConfig';
import { RawATConfig } from './model/RawATConfig';
import { GenerateATConfigError, InvalidInputDataFormat } from './errors/generateATErrors';
import { ATConfigValidator } from './contracts/ATConfigValidator';
import { SemanticConfig } from './types/SemanticConfig';

export class GenerateAutomaticTranslationsCofig {
private atuomaticTranslationConfigDS: ATConfigDataSource;

private templatsDS: TemplatesDataSource;

private validator: ATConfigValidator;

constructor(
atuomaticTranslationConfigDS: ATConfigDataSource,
templatesDS: TemplatesDataSource,
validator: ATConfigValidator
) {
this.atuomaticTranslationConfigDS = atuomaticTranslationConfigDS;
this.templatsDS = templatesDS;
this.validator = validator;
}

async execute(semanticConfig: SemanticConfig | unknown) {
if (!this.validator.validate(semanticConfig)) {
throw new InvalidInputDataFormat(this.validator.getErrors()[0]);
}

const templatesData = await this.templatsDS
.getByNames(semanticConfig.templates.map(t => t.template))
.all();

const templates = semanticConfig.templates.map(configData => {
const templateData = templatesData.find(t => t.name === configData.template);
if (!templateData) {
throw new GenerateATConfigError(`Template not found: ${configData.template}`);
}
return new ATTemplateConfig(
templateData?.id,
(configData.properties || []).map(label => {
const foundProperty = templateData.properties.find(p => p.label === label);
if (!foundProperty) {
throw new GenerateATConfigError(`Property not found: ${label}`);
}
return foundProperty.id;
}),
(configData.commonProperties || []).map(label => {
const foundProperty = templateData?.commonProperties.find(p => p.label === label);
if (!foundProperty) {
throw new GenerateATConfigError(`Common property not found: ${label}`);
}
return foundProperty.id;
})
);
});

return this.atuomaticTranslationConfigDS.update(
new RawATConfig(semanticConfig.active, templates)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RawATConfig } from '../model/RawATConfig';

export interface ATConfigDataSource {
get(): Promise<RawATConfig>;
update(config: RawATConfig): Promise<RawATConfig>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { SemanticConfig } from '../types/SemanticConfig';

export interface ATConfigValidator {
getErrors(): string[];
validate(data: unknown): data is SemanticConfig;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { LanguageISO6391 } from 'shared/types/commonTypes';

export interface ATGateway {
supportedLanguages(): Promise<LanguageISO6391[]>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* eslint-disable max-classes-per-file */
export class GenerateATConfigError extends Error {}
export class InvalidInputDataFormat extends Error {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Ajv } from 'ajv';
import { JTDSchemaType } from 'ajv/dist/core';
import { ATConfigValidator } from '../contracts/ATConfigValidator';
import { SemanticConfig } from '../types/SemanticConfig';

const schema: JTDSchemaType<SemanticConfig> = {
additionalProperties: false,
properties: {
active: { type: 'boolean' },
templates: {
elements: {
properties: {
template: { type: 'string' },
},
optionalProperties: {
properties: { elements: { type: 'string' } },
commonProperties: { elements: { type: 'string' } },
},
},
},
},
};

export class AJVATConfigValidator implements ATConfigValidator {
private errors: string[] = [];

getErrors() {
return this.errors;
}

validate(data: unknown) {
const ajv = new Ajv({ strict: false });
const validate = ajv.compile<SemanticConfig>(schema);
const result = validate(data);
this.errors = validate.errors ? validate.errors?.map(e => JSON.stringify(e.params)) : [];
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { LanguageISO6391 } from 'shared/types/commonTypes';
import { ATGateway } from '../contracts/ATGateway';

export class ATExternalAPI implements ATGateway {
// eslint-disable-next-line class-methods-use-this
async supportedLanguages() {
const result: LanguageISO6391[] = ['ru', 'en', 'fr', 'es'];
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { MongoDataSource } from 'api/common.v2/database/MongoDataSource';
import { Settings as SettingsType } from 'shared/types/settingsType';
import { ATConfigDataSource } from '../contracts/ATConfigDataSource';
import { RawATConfig } from '../model/RawATConfig';
import { ATTemplateConfig } from '../model/ATConfig';

export class MongoATConfigDataSource
extends MongoDataSource<SettingsType>
implements ATConfigDataSource
{
protected collectionName = 'settings';

async get() {
const settings = await this.getCollection().findOne();
const config = settings?.features?.automaticTranslation ?? { active: false };

return new RawATConfig(
config.active,
(config.templates || []).map(
t => new ATTemplateConfig(t.template, t.properties || [], t.commonProperties)
)
);
}

async update(config: RawATConfig) {
await this.getCollection().findOneAndUpdate(
{},
{ $set: { 'features.automaticTranslation': config } }
);
return this.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable max-classes-per-file */
import { LanguageISO6391 } from 'shared/types/commonTypes';

class ATTemplateConfig {
readonly template: string;

readonly commonProperties: string[];

readonly properties: string[];

constructor(template: string, properties: string[], commonProperties: string[] = []) {
this.template = template;
this.commonProperties = commonProperties;
this.properties = properties;
}
}

class ATConfig {
readonly active: boolean;

readonly languages: LanguageISO6391[];

readonly templates: ATTemplateConfig[];

constructor(active: boolean, languages: LanguageISO6391[], templates: ATTemplateConfig[]) {
this.active = active;
this.languages = languages;
this.templates = templates.filter(t => t.commonProperties.length || t.properties.length);
}
}

export { ATConfig, ATTemplateConfig };
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { LanguageISO6391 } from 'shared/types/commonTypes';
import { Property } from 'api/templates.v2/model/Property';
import { ATConfig, ATTemplateConfig } from './ATConfig';

class RawATConfig {
readonly active: boolean;

readonly templates: ATTemplateConfig[];

constructor(active: boolean, templates: ATTemplateConfig[]) {
this.active = active;
this.templates = templates;
}

getCompleteConfig(languages: LanguageISO6391[], validProperties: Property[]): ATConfig {
const validPropertiesMap = validProperties.reduce(
(memo, property) => {
// eslint-disable-next-line no-param-reassign
memo[property.id] = property;
return memo;
},
{} as { [k: string]: Property }
);

const validPropertiesIds = Object.keys(validPropertiesMap);

const templates = (this.templates || []).map(
templateConfig =>
new ATTemplateConfig(
templateConfig.template,
(templateConfig.properties || []).filter(
propertyId =>
validPropertiesIds.includes(propertyId) &&
validPropertiesMap[propertyId].template === templateConfig.template
),
templateConfig.commonProperties
)
);

return new ATConfig(this.active, languages, templates);
}
}

export { RawATConfig };
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { SettingsDataSource } from 'api/settings.v2/contracts/SettingsDataSource';
import { TemplatesDataSource } from 'api/templates.v2/contracts/TemplatesDataSource';
import { ATGateway } from '../contracts/ATGateway';
import { ATConfigDataSource } from '../contracts/ATConfigDataSource';

export class GetAutomaticTranslationConfig {
private settings: SettingsDataSource;

private config: ATConfigDataSource;

private templates: TemplatesDataSource;

private automaticTranslation: ATGateway;

constructor(
settings: SettingsDataSource,
config: ATConfigDataSource,
templates: TemplatesDataSource,
automaticTranslation: ATGateway
) {
this.settings = settings;
this.config = config;
this.templates = templates;
this.automaticTranslation = automaticTranslation;
}

async execute() {
const config = await this.config.get();

const validProperties = await this.templates.getAllTextProperties().all();
const configuredLanguages = await this.settings.getLanguageKeys();
const supportedLanguages = await this.automaticTranslation.supportedLanguages();

return config.getCompleteConfig(
configuredLanguages.filter(languageKey => supportedLanguages.includes(languageKey)),
validProperties
);
}
}
Loading

0 comments on commit fa6727a

Please sign in to comment.