diff --git a/api/package-lock.json b/api/package-lock.json index 39f229d231389f7912f2ead8aa189b0fb27a9b3e..74d865159a05949fc6f0951cf37a5f60a21ee877 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -21,6 +21,7 @@ "@nestjs/typeorm": "^8.0.3", "apollo-server-express": "^3.6.3", "axios": "^0.21.1", + "cache-manager": "^4.0.1", "cookie-parser": "^1.4.6", "graphql": "^15.5.3", "graphql-type-json": "^0.3.2", @@ -46,6 +47,7 @@ "@semantic-release/changelog": "^6.0.1", "@semantic-release/git": "^10.0.1", "@semantic-release/gitlab": "^7.0.4", + "@types/cache-manager": "^4.0.0", "@types/cookie-parser": "^1.4.2", "@types/express": "^4.17.13", "@types/jest": "^27.0.1", @@ -2573,6 +2575,12 @@ "@types/node": "*" } }, + "node_modules/@types/cache-manager": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/cache-manager/-/cache-manager-4.0.0.tgz", + "integrity": "sha512-uGnPOCM3PtlqZagds3i8mNyEwKLgZpKgswqmlF2ahmh4D1TN1aLYxYez2PDFDy42IGwLTbuHWSiF62I2jouM7g==", + "dev": true + }, "node_modules/@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -3637,6 +3645,11 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -4026,6 +4039,24 @@ "node": ">= 0.8" } }, + "node_modules/cache-manager": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-4.0.1.tgz", + "integrity": "sha512-JWdtjdX8e0e6eMehAZsdJvBMvHn/pVQGYUjgzc1ILFH0vtcffb9R7XIEAqfYgEeaVJVCOSP4+dxCius+ciW0RA==", + "dependencies": { + "async": "3.2.3", + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^7.10.1" + } + }, + "node_modules/cache-manager/node_modules/lru-cache": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.10.1.tgz", + "integrity": "sha512-BQuhQxPuRl79J5zSXRP+uNzPOyZw2oFI9JLRQ80XswSvg21KMKNtQza9eF42rfI/3Z40RvzBdXgziEkudzjo8A==", + "engines": { + "node": ">=12" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -8593,6 +8624,11 @@ "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", @@ -17318,6 +17354,12 @@ "@types/node": "*" } }, + "@types/cache-manager": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/cache-manager/-/cache-manager-4.0.0.tgz", + "integrity": "sha512-uGnPOCM3PtlqZagds3i8mNyEwKLgZpKgswqmlF2ahmh4D1TN1aLYxYez2PDFDy42IGwLTbuHWSiF62I2jouM7g==", + "dev": true + }, "@types/cacheable-request": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", @@ -18200,6 +18242,11 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true }, + "async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, "async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -18506,6 +18553,23 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, + "cache-manager": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-4.0.1.tgz", + "integrity": "sha512-JWdtjdX8e0e6eMehAZsdJvBMvHn/pVQGYUjgzc1ILFH0vtcffb9R7XIEAqfYgEeaVJVCOSP4+dxCius+ciW0RA==", + "requires": { + "async": "3.2.3", + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^7.10.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.10.1.tgz", + "integrity": "sha512-BQuhQxPuRl79J5zSXRP+uNzPOyZw2oFI9JLRQ80XswSvg21KMKNtQza9eF42rfI/3Z40RvzBdXgziEkudzjo8A==" + } + } + }, "cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -22003,6 +22067,11 @@ "integrity": "sha1-+CbJtOKoUR2E46yinbBeGk87cqk=", "dev": true }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "lodash.escaperegexp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", diff --git a/api/package.json b/api/package.json index 1e71e563cc9c3fa4fd9c0f1b13328690e937541c..41ac9d65ba74232143c5a2d9db79e93ef7ba5e2d 100644 --- a/api/package.json +++ b/api/package.json @@ -38,6 +38,7 @@ "@nestjs/typeorm": "^8.0.3", "apollo-server-express": "^3.6.3", "axios": "^0.21.1", + "cache-manager": "^4.0.1", "cookie-parser": "^1.4.6", "graphql": "^15.5.3", "graphql-type-json": "^0.3.2", @@ -63,6 +64,7 @@ "@semantic-release/changelog": "^6.0.1", "@semantic-release/git": "^10.0.1", "@semantic-release/gitlab": "^7.0.4", + "@types/cache-manager": "^4.0.0", "@types/cookie-parser": "^1.4.2", "@types/express": "^4.17.13", "@types/jest": "^27.0.1", diff --git a/api/src/auth/auth.resolver.spec.ts b/api/src/auth/auth.resolver.spec.ts index e545b55cb221ba157e2f5afcf44f86ac6b7fa7c1..7890a9e5bc8f52774599a6a66a14a9a54ee75a04 100644 --- a/api/src/auth/auth.resolver.spec.ts +++ b/api/src/auth/auth.resolver.spec.ts @@ -1,11 +1,9 @@ import { getMockRes } from '@jest-mock/express'; import { Test, TestingModule } from '@nestjs/testing'; import { MockFunctionMetadata, ModuleMocker } from 'jest-mock'; -import LocalService from '../engine/connectors/local/main.connector'; -import { - ENGINE_MODULE_OPTIONS, - ENGINE_SERVICE, -} from '../engine/engine.constants'; +import EngineService from '../engine/engine.service'; +import LocalService from '../engine/connectors/local/local.connector'; +import { ENGINE_MODULE_OPTIONS } from '../engine/engine.constants'; import { User } from '../users/models/user.model'; import { authConstants } from './auth-constants'; import { AuthResolver } from './auth.resolver'; @@ -40,7 +38,7 @@ describe('AuthResolver', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ { - provide: ENGINE_SERVICE, + provide: EngineService, useClass: LocalService, }, { diff --git a/api/src/auth/auth.resolver.ts b/api/src/auth/auth.resolver.ts index 4742be80f7a9237c6787596412a7618aa3bbb8b4..cf3208a99b76554b383a8dd3c181805f38d9ce06 100644 --- a/api/src/auth/auth.resolver.ts +++ b/api/src/auth/auth.resolver.ts @@ -10,11 +10,7 @@ import { Response, Request } from 'express'; import { CurrentUser } from '../common/decorators/user.decorator'; import { GQLRequest } from '../common/decorators/gql-request.decoractor'; import { GQLResponse } from '../common/decorators/gql-response.decoractor'; -import { - ENGINE_MODULE_OPTIONS, - ENGINE_SERVICE, -} from '../engine/engine.constants'; -import { IEngineOptions, IEngineService } from '../engine/engine.interfaces'; +import { ENGINE_MODULE_OPTIONS } from '../engine/engine.constants'; import { User } from '../users/models/user.model'; import { authConstants } from './auth-constants'; import { AuthService } from './auth.service'; @@ -23,6 +19,8 @@ import { LocalAuthGuard } from './guards/local-auth.guard'; import { AuthenticationInput } from './inputs/authentication.input'; import { AuthenticationOutput } from './outputs/authentication.output'; import { parseToBoolean } from '../common/utils/shared.utils'; +import EngineOptions from '../engine/interfaces/engine-options.interface'; +import EngineService from '../engine/engine.service'; //Custom defined type because Pick<CookieOptions, 'sameSite'> does not work type SameSiteType = boolean | 'lax' | 'strict' | 'none' | undefined; @@ -32,9 +30,9 @@ export class AuthResolver { private readonly logger = new Logger(AuthResolver.name); constructor( - @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, + private readonly engineService: EngineService, @Inject(ENGINE_MODULE_OPTIONS) - private readonly engineOptions: IEngineOptions, + private readonly engineOptions: EngineOptions, private readonly authService: AuthService, private readonly configService: ConfigService, ) {} @@ -82,7 +80,9 @@ export class AuthResolver { if (user) { this.logger.verbose(`${user.username} logged out`); try { - await this.engineService.logout?.(req); + if (this.engineService.has('logout')) { + await this.engineService.logout(req); + } } catch (e) { this.logger.debug( `Service ${this.engineOptions.type} produce an error when logging out ${user.username}`, diff --git a/api/src/auth/auth.service.spec.ts b/api/src/auth/auth.service.spec.ts index 454ce9230371e3a5e8bd01d7469ae25252dbd859..ea689bdf469a81b7e8bdc4d9429a87598406d05c 100644 --- a/api/src/auth/auth.service.spec.ts +++ b/api/src/auth/auth.service.spec.ts @@ -1,18 +1,23 @@ import { JwtService } from '@nestjs/jwt'; import { Test, TestingModule } from '@nestjs/testing'; import { MockFunctionMetadata, ModuleMocker } from 'jest-mock'; -import LocalService from '../engine/connectors/local/main.connector'; -import { - ENGINE_MODULE_OPTIONS, - ENGINE_SERVICE, -} from '../engine/engine.constants'; +import { ENGINE_MODULE_OPTIONS } from '../engine/engine.constants'; import { AuthService } from './auth.service'; import { User } from '../users/models/user.model'; +import EngineService from '../engine/engine.service'; const moduleMocker = new ModuleMocker(global); +type MockEngineService = Partial<Record<keyof EngineService, jest.Mock>>; + +const createEngineService = (): MockEngineService => ({ + login: jest.fn(), + has: jest.fn(), +}); + describe('AuthService', () => { - let service: AuthService; + let authService: AuthService; + let engineService: MockEngineService; const user: User = { id: 'dummy', username: 'dummy64', @@ -23,8 +28,8 @@ describe('AuthService', () => { const module: TestingModule = await Test.createTestingModule({ providers: [ { - provide: ENGINE_SERVICE, - useClass: LocalService, + provide: EngineService, + useValue: createEngineService(), }, { provide: ENGINE_MODULE_OPTIONS, @@ -52,17 +57,25 @@ describe('AuthService', () => { }) .compile(); - service = module.get<AuthService>(AuthService); + authService = module.get<AuthService>(AuthService); + engineService = module.get<EngineService>( + EngineService, + ) as unknown as MockEngineService; }); it('login', async () => { - const data = await service.login(user); + const data = await authService.login(user); expect(data.accessToken).toBe(jwtToken); }); it('validateUser', async () => { - const data = await service.validateUser('guest', 'password123'); + engineService.has.mockReturnValue(true); + engineService.login.mockReturnValue({ + id: '1', + username: 'dummy64', + }); + const data = await authService.validateUser('guest', 'password123'); expect(!!data).toBeTruthy(); }); diff --git a/api/src/auth/auth.service.ts b/api/src/auth/auth.service.ts index 402dfc63663a790d20462090114550ace92095b0..8300b9a1649ce7683d0559c2814dbde94fec16bc 100644 --- a/api/src/auth/auth.service.ts +++ b/api/src/auth/auth.service.ts @@ -1,20 +1,19 @@ -import { Inject, Injectable, NotImplementedException } from '@nestjs/common'; +import { Injectable, NotImplementedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import { ENGINE_SERVICE } from '../engine/engine.constants'; -import { IEngineService } from '../engine/engine.interfaces'; +import EngineService from '../engine/engine.service'; import { User } from '../users/models/user.model'; import { AuthenticationOutput } from './outputs/authentication.output'; @Injectable() export class AuthService { constructor( - @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, + private readonly engineService: EngineService, private jwtService: JwtService, ) {} async validateUser(username: string, password: string): Promise<User> { - if (!this.engineService.login) throw new NotImplementedException(); - return this.engineService.login?.(username, password); + if (!this.engineService.has('login')) throw new NotImplementedException(); + return this.engineService.login(username, password); } /** diff --git a/api/src/auth/strategies/engine.strategy.ts b/api/src/auth/strategies/engine.strategy.ts index f98cedecbbdbb0a2b5f05a426a5a15a0172b713a..7bf68c2df41d842645f11ce8bb36891213ec6f84 100644 --- a/api/src/auth/strategies/engine.strategy.ts +++ b/api/src/auth/strategies/engine.strategy.ts @@ -1,20 +1,17 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; -import { ENGINE_SERVICE } from 'src/engine/engine.constants'; -import { IEngineService } from 'src/engine/engine.interfaces'; import { Request } from 'express'; import { Strategy } from 'passport-custom'; +import EngineService from '../../engine/engine.service'; @Injectable() export class EngineStrategy extends PassportStrategy(Strategy, 'engine') { - constructor( - @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, - ) { + constructor(private readonly engineService: EngineService) { super(); } async validate(req: Request) { - if (!this.engineService.getActiveUser) return false; + if (!this.engineService.has('getActiveUser')) return false; const user = this.engineService.getActiveUser(req); return user ?? false; diff --git a/api/src/config/cache.config.ts b/api/src/config/cache.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..763d7d5b3bcc73dbdfeabdebdba94f4837b1f8ed --- /dev/null +++ b/api/src/config/cache.config.ts @@ -0,0 +1,12 @@ +import { registerAs } from '@nestjs/config'; +import { parseToBoolean } from '../common/utils/shared.utils'; + +export default registerAs('cache', () => { + const max = process.env.CACHE_MAX_ITEMS; + const ttl = process.env.CACHE_TTL; + return { + enabled: parseToBoolean(process.env.CACHE_ENABLED, false), + ttl: ttl ? parseInt(ttl) : undefined, + max: max ? parseInt(max) : undefined, + }; +}); diff --git a/api/src/engine/connectors/csv/main.connector.ts b/api/src/engine/connectors/csv/csv.connector.ts similarity index 92% rename from api/src/engine/connectors/csv/main.connector.ts rename to api/src/engine/connectors/csv/csv.connector.ts index ebb2ee15b229eea60e6e75c3f70160f6a3fae249..2d3a80cce5935712fdb5663ce50f8239d45bddb7 100644 --- a/api/src/engine/connectors/csv/main.connector.ts +++ b/api/src/engine/connectors/csv/csv.connector.ts @@ -5,15 +5,16 @@ import { Dictionary, ExperimentResult, } from 'src/common/interfaces/utilities.interface'; -import { IEngineOptions, IEngineService } from 'src/engine/engine.interfaces'; +import Connector from 'src/engine/interfaces/connector.interface'; +import EngineOptions from 'src/engine/interfaces/engine-options.interface'; import { Domain } from 'src/engine/models/domain.model'; import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; import { Group } from 'src/engine/models/group.model'; import { User } from 'src/users/models/user.model'; -export default class CSVService implements IEngineService { +export default class CSVConnector implements Connector { constructor( - private readonly options: IEngineOptions, + private readonly options: EngineOptions, private readonly httpService: HttpService, ) {} diff --git a/api/src/engine/connectors/datashield/main.connector.ts b/api/src/engine/connectors/datashield/datashield.connector.ts similarity index 91% rename from api/src/engine/connectors/datashield/main.connector.ts rename to api/src/engine/connectors/datashield/datashield.connector.ts index ef0a5435be672b66b77822be656f380211b0ba28..e9d325e722b3d3ee3fee0d44597ffabdf2914574 100644 --- a/api/src/engine/connectors/datashield/main.connector.ts +++ b/api/src/engine/connectors/datashield/datashield.connector.ts @@ -8,11 +8,9 @@ import { } from 'src/common/interfaces/utilities.interface'; import { errorAxiosHandler } from 'src/common/utils/shared.utils'; import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; -import { - IConfiguration, - IEngineOptions, - IEngineService, -} from 'src/engine/engine.interfaces'; +import ConnectorConfiguration from 'src/engine/interfaces/connector-configuration.interface'; +import Connector from 'src/engine/interfaces/connector.interface'; +import EngineOptions from 'src/engine/interfaces/engine-options.interface'; import { Domain } from 'src/engine/models/domain.model'; import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; import { Experiment } from 'src/engine/models/experiment/experiment.model'; @@ -31,15 +29,15 @@ import { transformToTable, } from './transformations'; -export default class DataShieldService implements IEngineService { - private static readonly logger = new Logger(DataShieldService.name); +export default class DataShieldConnector implements Connector { + private static readonly logger = new Logger(DataShieldConnector.name); headers = {}; constructor( - @Inject(ENGINE_MODULE_OPTIONS) private readonly options: IEngineOptions, + @Inject(ENGINE_MODULE_OPTIONS) private readonly options: EngineOptions, private readonly httpService: HttpService, ) {} - getConfiguration(): IConfiguration { + getConfiguration(): ConnectorConfiguration { return { hasGalaxy: false, hasGrouping: false, @@ -104,8 +102,8 @@ export default class DataShieldService implements IEngineService { ); if (response.data['global'] === undefined) { - DataShieldService.logger.warn('Cannot parse histogram result'); - DataShieldService.logger.verbose(path); + DataShieldConnector.logger.warn('Cannot parse histogram result'); + DataShieldConnector.logger.verbose(path); return { rawdata: { data: diff --git a/api/src/engine/connectors/exareme/converters.ts b/api/src/engine/connectors/exareme/converters.ts index b25282036fd29e191bf130d7779b8c9fe58f07b6..86b58070bbca5bfcada6303b2ec4806b6df9ced8 100644 --- a/api/src/engine/connectors/exareme/converters.ts +++ b/api/src/engine/connectors/exareme/converters.ts @@ -5,8 +5,6 @@ import { Experiment, ExperimentStatus, } from 'src/engine/models/experiment/experiment.model'; -import { AlgorithmParamInput } from 'src/experiments/models/input/algorithm-parameter.input'; -import { ExperimentCreateInput } from 'src/experiments/models/input/experiment-create.input'; import { Group } from 'src/engine/models/group.model'; import { ResultUnion } from 'src/engine/models/result/common/result-union.model'; import { @@ -17,6 +15,8 @@ import { HeatMapResult } from 'src/engine/models/result/heat-map-result.model'; import { LineChartResult } from 'src/engine/models/result/line-chart-result.model'; import { RawResult } from 'src/engine/models/result/raw-result.model'; import { Variable } from 'src/engine/models/variable.model'; +import { AlgorithmParamInput } from 'src/experiments/models/input/algorithm-parameter.input'; +import { ExperimentCreateInput } from 'src/experiments/models/input/experiment-create.input'; import { Entity } from './interfaces/entity.interface'; import { ExperimentData } from './interfaces/experiment/experiment.interface'; import { ResultChartExperiment } from './interfaces/experiment/result-chart-experiment.interface'; diff --git a/api/src/engine/connectors/exareme/main.connector.ts b/api/src/engine/connectors/exareme/exareme.connector.ts similarity index 95% rename from api/src/engine/connectors/exareme/main.connector.ts rename to api/src/engine/connectors/exareme/exareme.connector.ts index a2ca7a392434df5eca124b1167244730dd299160..60acbd5f9042b11f1c3c562d0b95ea18b57ebcc6 100644 --- a/api/src/engine/connectors/exareme/main.connector.ts +++ b/api/src/engine/connectors/exareme/exareme.connector.ts @@ -11,11 +11,9 @@ import { AxiosRequestConfig } from 'axios'; import { Request } from 'express'; import { firstValueFrom, map, Observable } from 'rxjs'; import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; -import { - IConfiguration, - IEngineOptions, - IEngineService, -} from 'src/engine/engine.interfaces'; +import ConnectorConfiguration from 'src/engine/interfaces/connector-configuration.interface'; +import Connector from 'src/engine/interfaces/connector.interface'; +import EngineOptions from 'src/engine/interfaces/engine-options.interface'; import { Domain } from 'src/engine/models/domain.model'; import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; import { @@ -47,9 +45,9 @@ import transformToAlgorithms from './transformations/algorithms'; type Headers = Record<string, string>; @Injectable() -export default class ExaremeService implements IEngineService { +export default class ExaremeConnector implements Connector { constructor( - @Inject(ENGINE_MODULE_OPTIONS) private readonly options: IEngineOptions, + @Inject(ENGINE_MODULE_OPTIONS) private readonly options: EngineOptions, private readonly httpService: HttpService, ) {} @@ -66,7 +64,7 @@ export default class ExaremeService implements IEngineService { ]; } - getConfiguration(): IConfiguration { + getConfiguration(): ConnectorConfiguration { return { contactLink: 'https://ebrains.eu/support/', hasGalaxy: true, diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts index 7b414bda9c4ae870c272c0c1fce525327fb613fa..d2a299b68ae5de8399e3c4116548d2303a04e971 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.spec.ts @@ -78,7 +78,7 @@ describe('PCA result handler', () => { it('Test PCA handler with regular data (no edge cases)', () => { const exp = createExperiment(); - handlers(exp, data); + handlers(exp, data, null); expect(exp.results.length).toBeGreaterThanOrEqual(2); exp.results.forEach((it) => { diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts index bc55d0dc7b83b6c92f2ac6ce5ffa4d7831736d07..0de41ca99cd43c14bfb389c58ee28934dcb49f78 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.spec.ts @@ -140,7 +140,7 @@ describe('Anova oneway result handler', () => { const table2 = anovaHandler.getTuckeyTable(data); const meanPlot = anovaHandler.getMeanPlot(data); - handlers(exp, data); + handlers(exp, data, null); expect(exp.results.length).toBeGreaterThanOrEqual(3); expect(exp.results).toContainEqual(table1); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts index 42ff6da38781827fe8393159aac7c8e35840a8af..d52d088f9234d00d5aa06329b9d7d49e71a002a8 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/anova-one-way.handler.ts @@ -1,4 +1,5 @@ import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' +import { Domain } from 'src/engine/models/domain.model'; import { MeanChartResult } from 'src/engine/models/result/means-chart-result.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { @@ -97,8 +98,9 @@ export default class AnovaOneWayHandler extends BaseHandler { return AnovaOneWayHandler.meanPlotTransform.evaluate(data); } - handle(exp: Experiment, data: unknown): void { - if (!this.canHandle(exp.algorithm.name)) return super.handle(exp, data); + handle(exp: Experiment, data: unknown, domain: Domain): void { + if (!this.canHandle(exp.algorithm.name)) + return super.handle(exp, data, domain); const summaryTable = this.getSummaryTable(data, exp.coVariables[0]); if (summaryTable) exp.results.push(summaryTable); @@ -109,6 +111,6 @@ export default class AnovaOneWayHandler extends BaseHandler { const meanPlot = this.getMeanPlot(data); if (meanPlot && meanPlot.pointCIs) exp.results.push(meanPlot); - return super.handle(exp, data); // continue request + return super.handle(exp, data, domain); // continue request } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/area.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/area.handler.ts deleted file mode 100644 index 6e096e31c85d37193089acc4039f2a949c2dfd2e..0000000000000000000000000000000000000000 --- a/api/src/engine/connectors/exareme/handlers/algorithms/area.handler.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' -import { Experiment } from '../../../../../engine/models/experiment/experiment.model'; -import { ResultChartExperiment } from '../../interfaces/experiment/result-chart-experiment.interface'; -import BaseHandler from '../base.handler'; - -export default class AreaHandler extends BaseHandler { - private static readonly transform = jsonata(` - ({ - "name": data.title.text, - "xAxis": { - "label": data.xAxis.title.text - }, - "yAxis": { - "label": data.yAxis.title.text - }, - "lines": [ - { - "label": "ROC curve", - "x": data.series.data.$[0], - "y": data.series.data.$[1], - "type": 0 - } - ] - }) - `); - - canHandle(input: ResultChartExperiment): boolean { - try { - return ( - input.type === 'application/vnd.highcharts+json' && - input.data?.chart?.type === 'area' - ); - } catch (e) { - AreaHandler.logger.log('Error when parsing input from experiment'); - AreaHandler.logger.debug(e); - return false; - } - } - - handle(exp: Experiment, data: unknown): void { - let req = data; - const inputs = data as ResultChartExperiment[]; - - if (inputs && Array.isArray(inputs)) { - inputs - .filter(this.canHandle) - .map((input) => AreaHandler.transform.evaluate(input)) - .forEach((input) => exp.results.push(input)); - - req = JSON.stringify(inputs.filter((input) => !this.canHandle(input))); - } - - this.next?.handle(exp, req); - } -} diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/heat-map.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/heat-map.handler.ts deleted file mode 100644 index 47d937a04a5e0223ba6b636cd5cfe7094076dc8a..0000000000000000000000000000000000000000 --- a/api/src/engine/connectors/exareme/handlers/algorithms/heat-map.handler.ts +++ /dev/null @@ -1,46 +0,0 @@ -import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' -import { Experiment } from '../../../../models/experiment/experiment.model'; -import { ResultChartExperiment } from '../../interfaces/experiment/result-chart-experiment.interface'; -import BaseHandler from '../base.handler'; - -export default class HeatMapHandler extends BaseHandler { - private static readonly transform = jsonata(` - ( - { - "name": data.title.text, - "xAxis": { - "categories": data.xAxis.categories, - "label": data.xAxis.label - }, - "yAxis": { - "categories": data.yAxis.categories, - "label": data.yAxis.label - }, - "matrix": $toMat(data.series.data) - } - ) - `); - - canHandle(input: ResultChartExperiment): boolean { - return ( - input.type.toLowerCase() === 'application/vnd.highcharts+json' && - input.data.chart.type.toLowerCase() === 'heatmap' - ); - } - - handle(exp: Experiment, data: unknown): void { - let req = data; - const inputs = data as ResultChartExperiment[]; - - if (inputs && Array.isArray(inputs)) { - inputs - .filter(this.canHandle) - .map((input) => HeatMapHandler.transform.evaluate(input)) - .forEach((input) => exp.results.push(input)); - - req = JSON.stringify(inputs.filter((input) => !this.canHandle(input))); - } - - this.next?.handle(exp, req); - } -} diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.spec.ts index 3c29918a110de73ac3b02b7d3a0994ce607efe24..c2d6561e6268d278e3da3eed70a8264c67033ac0 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.spec.ts @@ -1,3 +1,4 @@ +import { Domain } from 'src/engine/models/domain.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import LinearRegressionHandler from './linear-regression.handler'; @@ -22,6 +23,20 @@ const data = { upper_ci: [0.2755544764379397, 0.6451975487993219, 1.132126625132179], }; +const domain: Domain = { + id: 'dummy-id', + groups: [], + rootGroup: { + id: 'dummy-id', + }, + datasets: [{ id: 'desd-synthdata', label: 'Dead Synthdata' }], + variables: [ + { id: 'lefthippocampus', label: 'Left Hippo Campus' }, + { id: 'righthippocampus', label: 'Right Hippo Campus' }, + { id: 'leftamygdala', label: 'Left Amygdala' }, + ], +}; + const createExperiment = (): Experiment => ({ id: 'dummy-id', name: 'Testing purpose', @@ -30,8 +45,8 @@ const createExperiment = (): Experiment => ({ }, datasets: ['desd-synthdata'], domain: 'dementia', - variables: ['righthippocampus'], - coVariables: ['leftamygdala'], + variables: ['lefthippocampus'], + coVariables: ['righthippocampus', 'leftamygdala'], results: [], }); @@ -46,13 +61,16 @@ describe('Linear regression result handler', () => { describe('Handle', () => { it('with standard linear algo data', () => { - linearHandler.handle(experiment, data); + linearHandler.handle(experiment, data, domain); + + const json = JSON.stringify(experiment.results); + expect(json.includes(domain.variables[0].label)).toBeTruthy(); expect(experiment.results.length === 2); }); it('Should be empty with another algo', () => { experiment.algorithm.name = 'dummy_algo'; - linearHandler.handle(experiment, data); + linearHandler.handle(experiment, data, domain); expect(experiment.results.length === 0); }); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.ts index dc9236ff40ee25e7522d629c4cea568e75fa00d5..68266f9376f6523b82de120e333bbb1df17674b4 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression.handler.ts @@ -1,3 +1,5 @@ +import { Domain } from 'src/engine/models/domain.model'; +import { Variable } from 'src/engine/models/variable.model'; import { isNumber } from '../../../../../common/utils/shared.utils'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { @@ -7,7 +9,7 @@ import { import BaseHandler from '../base.handler'; const NUMBER_PRECISION = 4; -const ALGO_NANE = 'linear_regression'; +const ALGO_NAME = 'linear_regression'; const lookupDict = { dependent_var: 'Dependent variable', n_obs: 'Number of observations', @@ -88,14 +90,29 @@ export default class LinearRegressionHandler extends BaseHandler { return tableCoef; } - handle(experiment: Experiment, data: any): void { - if (experiment.algorithm.name.toLowerCase() !== ALGO_NANE) - return super.handle(experiment, data); + getLabelFromVariableId(id: string, vars: Variable[]): string { + const varible = vars.find((v) => v.id === id); + return varible.label ?? id; + } + + handle(experiment: Experiment, data: any, domain: Domain): void { + if (experiment.algorithm.name.toLowerCase() !== ALGO_NAME) + return super.handle(experiment, data, domain); + + const varIds = [...experiment.variables, ...(experiment.coVariables ?? [])]; + const variables = domain.variables.filter((v) => varIds.includes(v.id)); + + let jsonData = JSON.stringify(data); + variables.forEach((v) => { + const regEx = new RegExp(v.id, 'gi'); + jsonData = jsonData.replaceAll(regEx, v.label); + }); + const improvedData = JSON.parse(jsonData); - const model = this.getModel(data); + const model = this.getModel(improvedData); if (model) experiment.results.push(model); - const coefs = this.getCoefficients(data); + const coefs = this.getCoefficients(improvedData); if (coefs) experiment.results.push(coefs); } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts index 36a2759dff39197636dad6efbbaee39d37d2e7ce..17abd4315ce007d48502d9924416720d50b2ba91 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.spec.ts @@ -423,7 +423,7 @@ describe('Pearson result handler', () => { 'high_confidence_intervals', ]; - handlers(exp, data); + handlers(exp, data, null); const results = exp.results as HeatMapResult[]; const heatmaps = names.map((name) => diff --git a/api/src/engine/connectors/exareme/handlers/base.handler.ts b/api/src/engine/connectors/exareme/handlers/base.handler.ts index 5934cf82008004b964a52601b2af21c08d9357d3..1aa3d2f6391922b10e1d9b29bf9c9a74fef0d575 100644 --- a/api/src/engine/connectors/exareme/handlers/base.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/base.handler.ts @@ -1,4 +1,5 @@ import { Logger } from '@nestjs/common'; +import { Domain } from 'src/engine/models/domain.model'; import { Experiment } from '../../../models/experiment/experiment.model'; import ResultHandler from './result-handler.interface'; @@ -12,7 +13,7 @@ export default abstract class BaseHandler implements ResultHandler { return h; } - handle(experiment: Experiment, data: unknown): void { - this.next?.handle(experiment, data); + handle(experiment: Experiment, data: unknown, domain: Domain): void { + this.next?.handle(experiment, data, domain); } } diff --git a/api/src/engine/connectors/exareme/handlers/index.ts b/api/src/engine/connectors/exareme/handlers/index.ts index 1dca0fce5e88d570f1f7f754be57a30b766d1045..d45dae7f3e0990ec3fe4255d12b46fa372a8a6ad 100644 --- a/api/src/engine/connectors/exareme/handlers/index.ts +++ b/api/src/engine/connectors/exareme/handlers/index.ts @@ -1,8 +1,7 @@ +import { Domain } from 'src/engine/models/domain.model'; import { Experiment } from '../../../../engine/models/experiment/experiment.model'; import AnovaOneWayHandler from './algorithms/anova-one-way.handler'; -import AreaHandler from './algorithms/area.handler'; import DescriptiveHandler from './algorithms/descriptive.handler'; -import HeatMapHandler from './algorithms/heat-map.handler'; import LinearRegressionHandler from './algorithms/linear-regression.handler'; import PCAHandler from './algorithms/PCA.handler'; import PearsonHandler from './algorithms/pearson.handler'; @@ -11,15 +10,13 @@ import RawHandler from './algorithms/raw.handler'; const start = new PearsonHandler(); start - .setNext(new AreaHandler()) .setNext(new DescriptiveHandler()) - .setNext(new HeatMapHandler()) .setNext(new AnovaOneWayHandler()) .setNext(new PCAHandler()) .setNext(new LinearRegressionHandler()) .setNext(new RawHandler()); // should be last handler as it works as a fallback (if other handlers could not process the results) -export default (exp: Experiment, data: unknown): Experiment => { +export default (exp: Experiment, data: unknown, domain: Domain): Experiment => { start.handle(exp, data); return exp; }; diff --git a/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts b/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts index 661c2a437015b8a75950394a2f27380602cbb25b..3c82f7d32ca56246281bcb09c996565d77a518eb 100644 --- a/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts +++ b/api/src/engine/connectors/exareme/handlers/result-handler.interface.ts @@ -1,7 +1,8 @@ +import { Domain } from 'src/engine/models/domain.model'; import { Experiment } from '../../../models/experiment/experiment.model'; // produce algo handler export default interface ResultHandler { setNext(h: ResultHandler): ResultHandler; - handle(partialExperiment: Experiment, data: unknown): void; + handle(partialExperiment: Experiment, data: unknown, domain?: Domain): void; } diff --git a/api/src/engine/connectors/exareme/interfaces/test-utilities.ts b/api/src/engine/connectors/exareme/interfaces/test-utilities.ts index 6746a8f2b736a9e9668504e98f5b54aa83563cd9..26596046209d10901b4467ca5c778baa17cca69b 100644 --- a/api/src/engine/connectors/exareme/interfaces/test-utilities.ts +++ b/api/src/engine/connectors/exareme/interfaces/test-utilities.ts @@ -1,6 +1,6 @@ -import { IEngineService } from 'src/engine/engine.interfaces'; -import { Experiment } from 'src/engine/models/experiment/experiment.model'; -import { ExperimentCreateInput } from 'src/experiments/models/input/experiment-create.input'; +import EngineService from '../../../engine.service'; +import { Experiment } from '../../../models/experiment/experiment.model'; +import { ExperimentCreateInput } from '../../../../experiments/models/input/experiment-create.input'; const TIMEOUT_DURATION_SECONDS = 60 * 10; @@ -28,14 +28,14 @@ const TEST_PATHOLOGIES = { const createExperiment = async ( input: ExperimentCreateInput, - service: IEngineService, + service: EngineService, ): Promise<Experiment | undefined> => { return service.createExperiment(input, false); }; const waitForResult = ( id: string, - service: IEngineService, + service: EngineService, ): Promise<Experiment> => new Promise((resolve, reject) => { let elapsed = 0; diff --git a/api/src/engine/connectors/exareme/tests/e2e/3c.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/3c.e2e-spec.ts index 7b4d577e146933477f7eb5507da49860f2a03463..e64ca3e15f6eb8d66a83ef6baad8628bbf2a642e 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/3c.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/3c.e2e-spec.ts @@ -1,7 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { createExperiment, @@ -10,18 +8,19 @@ import { TIMEOUT_DURATION_SECONDS, waitForResult, } from '../../interfaces/test-utilities'; +import EngineService from '../../../../../engine/engine.service'; jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `3c-${generateNumber()}`; diff --git a/api/src/engine/connectors/exareme/tests/e2e/calibration-belt.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/calibration-belt.e2e-spec.ts index 2e696f066825ade2f7ba74e6fe3d091f8a77d4ca..254e03e012adf6fa8fced8486864034a04830e98 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/calibration-belt.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/calibration-belt.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { createExperiment, generateNumber, @@ -14,14 +13,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `calibration-belt-${generateNumber()}`; diff --git a/api/src/engine/connectors/exareme/tests/e2e/cart.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/cart.e2e-spec.ts index 550e75c2da6abb999d06b88a72d068d548d3b0e0..639f1d3085b3563e86b32a779559fa221bad41cd 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/cart.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/cart.e2e-spec.ts @@ -1,9 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { RawResult } from 'src/engine/models/result/raw-result.model'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; +import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, generateNumber, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `cart-${generateNumber()}`; const algorithmId = 'CART'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/descriptiveStatistics.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/descriptiveStatistics.e2e-spec.ts index 12737e18d39cf0bfd21dbd5828b36ea687627cbd..3d549536f2e7a6b56f017e8122e4f110cb594db2 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/descriptiveStatistics.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/descriptiveStatistics.e2e-spec.ts @@ -1,10 +1,9 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { GroupsResult } from 'src/engine/models/result/groups-result.model'; -import { TableResult } from 'src/engine/models/result/table-result.model'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; +import { GroupsResult } from '../../../../models/result/groups-result.model'; +import { TableResult } from '../../../../models/result/table-result.model'; import { createExperiment, generateNumber, @@ -16,14 +15,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `statistics-${generateNumber()}`; const algorithmId = 'DESCRIPTIVE_STATS'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/id3.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/id3.e2e-spec.ts index 6a34e183b7127d893019b7950c30228965feb417..922734f5fe6d9ab089e181a1c34d5f7e96853a79 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/id3.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/id3.e2e-spec.ts @@ -1,9 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { RawResult } from 'src/engine/models/result/raw-result.model'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; +import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, generateNumber, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `id3-${generateNumber()}`; const algorithmId = 'ID3'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/k-means.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/k-means.e2e-spec.ts index b754a068478c679d04158f4776d14ae041063fd0..7253261295736adce6de2de271ec35f2d2ce936a 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/k-means.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/k-means.e2e-spec.ts @@ -1,9 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { RawResult } from 'src/engine/models/result/raw-result.model'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; +import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, generateNumber, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `kmeans-${generateNumber()}`; const algorithmId = 'KMEANS'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/kaplan-meier.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/kaplan-meier.e2e-spec.ts index 2defac942a734cf22d9bd2c5442d5d13012f2ad5..e0e6e41f33b2308ff1395d9b5553376a27f1472a 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/kaplan-meier.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/kaplan-meier.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { createExperiment, generateNumber, @@ -14,14 +13,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `kaplan-meier-${generateNumber()}`; const algorithmId = 'KAPLAN_MEIER'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/linear-regression.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/linear-regression.e2e-spec.ts index e50a0ad83a18a9dadd2025daa2a06db7abe39b67..928ee3b790586126acdf01b6dfc0461b67a6d975 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/linear-regression.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/linear-regression.e2e-spec.ts @@ -1,9 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { RawResult } from 'src/engine/models/result/raw-result.model'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; +import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, generateNumber, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `linear-${generateNumber()}`; const algorithmId = 'LINEAR_REGRESSION'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/logistic-regression.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/logistic-regression.e2e-spec.ts index 50bc9e66a37b0ed4d66c375690df53890d8a74e5..9ca333a190ae0b4419adc04712bc32f4150de18f 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/logistic-regression.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/logistic-regression.e2e-spec.ts @@ -1,9 +1,8 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { RawResult } from 'src/engine/models/result/raw-result.model'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; +import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, generateNumber, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `logistic-${generateNumber()}`; const algorithmId = 'LOGISTIC_REGRESSION'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/multiple-histograms.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/multiple-histograms.e2e-spec.ts index 8eba2965e0422dc52ab85785fb8841225d9b8dd0..f1ad948db341733b4892aa95ddbcbfe709381030 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/multiple-histograms.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/multiple-histograms.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { createExperiment, generateNumber, @@ -14,14 +13,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `histograms-${generateNumber()}`; const algorithmId = 'MULTIPLE_HISTOGRAMS'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/naive-bayes.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/naive-bayes.e2e-spec.ts index cefa59cb015af49116d53836e23541e393fbff9f..893a7af2f7cc1ae2e5e36fe932df0fb1ba81a55c 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/naive-bayes.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/naive-bayes.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `naivebayes-${generateNumber()}`; const algorithmId = 'NAIVE_BAYES'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/one-way-anova.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/one-way-anova.e2e-spec.ts index 3f954f584b4397cf2d0c1e1a40904cde758314bb..54b9b31a10d8f94facd43c1bf8d8a8bffeea1661 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/one-way-anova.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/one-way-anova.e2e-spec.ts @@ -1,9 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; -import { RawResult } from '../../../../models/result/raw-result.model'; +import { AppModule } from '../../../../../main/app.module'; import { createExperiment, generateNumber, @@ -15,14 +13,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `anova-1way-${generateNumber()}`; const algorithmId = 'ANOVA_ONEWAY'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/pca.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/pca.e2e-spec.ts index 36f78cfd0b876c1e2f42a22775d3fa2a634e0244..9c23cbbb9bf454c55c15c293c41d0adb1ccc36f6 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/pca.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/pca.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `pca-${generateNumber()}`; const algorithmId = 'PCA'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/pearson-correlation.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/pearson-correlation.e2e-spec.ts index 8bc3f92e4d1bbecdade74e1e71f200851ec37aba..dd36eac8d92b0a60f1e2563c8a1d55e56052eabf 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/pearson-correlation.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/pearson-correlation.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `pearson-${generateNumber()}`; const algorithmId = 'PEARSON_CORRELATION'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/t-test-independant.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/t-test-independant.e2e-spec.ts index 208516e44e45e53dabce70264c1593981850dc64..a15c9ace53b6292dfab0a02fb59edc20656ff27c 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/t-test-independant.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/t-test-independant.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `ttest-idp-${generateNumber()}`; const algorithmId = 'TTEST_INDEPENDENT'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/t-test-one-sample.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/t-test-one-sample.e2e-spec.ts index c9cf5af84eaa172671cf173be42e48c156498f9a..c49169d6d3bb08d337fc6b6a1516f18cf6800423 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/t-test-one-sample.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/t-test-one-sample.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `ttest-1s-${generateNumber()}`; const algorithmId = 'TTEST_ONESAMPLE'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/t-test-paired.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/t-test-paired.e2e-spec.ts index 45d2220683082f9c67656d4751f2990fc74e52d8..95fa4c3fa4d74d9f0904e9f4171fc81e5e2edc32 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/t-test-paired.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/t-test-paired.e2e-spec.ts @@ -1,8 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; +import EngineService from '../../../../../engine/engine.service'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; +import { AppModule } from '../../../../../main/app.module'; import { RawResult } from '../../../../models/result/raw-result.model'; import { createExperiment, @@ -15,14 +14,14 @@ import { jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `ttest-paired-${generateNumber()}`; const algorithmId = 'TTEST_PAIRED'; diff --git a/api/src/engine/connectors/exareme/tests/e2e/two-way-anova.e2e-spec.ts b/api/src/engine/connectors/exareme/tests/e2e/two-way-anova.e2e-spec.ts index 021723c69dad30ec292b656d128be0bfc34598a1..80c65495461375baf6bca3714778402480aa2b66 100644 --- a/api/src/engine/connectors/exareme/tests/e2e/two-way-anova.e2e-spec.ts +++ b/api/src/engine/connectors/exareme/tests/e2e/two-way-anova.e2e-spec.ts @@ -1,7 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; import { AppModule } from '../../../../../main/app.module'; -import { ENGINE_SERVICE } from '../../../../engine.constants'; -import { IEngineService } from '../../../../engine.interfaces'; import { ExperimentCreateInput } from '../../../../../experiments/models/input/experiment-create.input'; import { RawResult } from '../../../../models/result/raw-result.model'; import { @@ -11,18 +9,19 @@ import { TIMEOUT_DURATION_SECONDS, waitForResult, } from '../../interfaces/test-utilities'; +import EngineService from '../../../../../engine/engine.service'; jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('ExaremeService', () => { - let exaremeService: IEngineService; + let exaremeService: EngineService; beforeEach(async () => { const moduleRef: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); - exaremeService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + exaremeService = await moduleRef.resolve<EngineService>(EngineService); }); const modelSlug = `anova-2way-${generateNumber()}`; const algorithmId = 'ANOVA'; diff --git a/api/src/engine/connectors/local/main.connector.ts b/api/src/engine/connectors/local/local.connector.ts similarity index 87% rename from api/src/engine/connectors/local/main.connector.ts rename to api/src/engine/connectors/local/local.connector.ts index ef95f6049d396ec95d6ac4d81269ccad7d0b16b3..e19fb48cf5f7653deac3ac1924bf99183b21df07 100644 --- a/api/src/engine/connectors/local/main.connector.ts +++ b/api/src/engine/connectors/local/local.connector.ts @@ -1,10 +1,10 @@ -import { IEngineService } from 'src/engine/engine.interfaces'; +import Connector from 'src/engine/interfaces/connector.interface'; import { Domain } from 'src/engine/models/domain.model'; import { Algorithm } from 'src/engine/models/experiment/algorithm.model'; import { ResultUnion } from 'src/engine/models/result/common/result-union.model'; import { User } from 'src/users/models/user.model'; -export default class LocalService implements IEngineService { +export default class LocalConnector implements Connector { async login(): Promise<User> { return { id: '1', @@ -20,7 +20,7 @@ export default class LocalService implements IEngineService { throw new Error('Method not implemented.'); } - getDomains(): Domain[] { + async getDomains(): Promise<Domain[]> { return [ { id: 'Dummy', diff --git a/api/src/engine/engine.constants.ts b/api/src/engine/engine.constants.ts index 641ceed91bb4a29953fc558479f5b667fd112808..351c3aa3a3258603cf674a090d61da9bd3ab0ba1 100644 --- a/api/src/engine/engine.constants.ts +++ b/api/src/engine/engine.constants.ts @@ -1,4 +1,3 @@ export const ENGINE_MODULE_OPTIONS = 'EngineModuleOption'; -export const ENGINE_SERVICE = 'EngineService'; export const ENGINE_SKIP_TOS = 'TOS_SKIP'; export const ENGINE_ONTOLOGY_URL = 'ONTOLOGY_URL'; diff --git a/api/src/engine/engine.controller.ts b/api/src/engine/engine.controller.ts index fc656aabb1d5d1c9f21e4a14af9cba01a5ee7440..d47b63b20b2851715d8b58e63c90851470547fca 100644 --- a/api/src/engine/engine.controller.ts +++ b/api/src/engine/engine.controller.ts @@ -1,19 +1,17 @@ -import { Controller, Get, Inject, Req, UseInterceptors } from '@nestjs/common'; +import { Controller, Get, Req, UseInterceptors } from '@nestjs/common'; import { Request } from 'express'; import { Observable } from 'rxjs'; -import { ENGINE_SERVICE } from './engine.constants'; -import { IEngineService } from './engine.interfaces'; +import EngineService from './engine.service'; import { ErrorsInterceptor } from './interceptors/errors.interceptor'; @UseInterceptors(ErrorsInterceptor) @Controller() export class EngineController { - constructor( - @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, - ) {} + constructor(private readonly engineService: EngineService) {} @Get('galaxy') galaxy(@Req() request: Request): Observable<string> | string { - return this.engineService.getPassthrough?.('galaxy', request); + if (this.engineService.has('getPassthrough')) + return this.engineService.getPassthrough('galaxy', request); } } diff --git a/api/src/engine/engine.module.ts b/api/src/engine/engine.module.ts index d636740a3a39e2e5901c96d60690ef365ac00b3c..6e359c9d8ba268c199bb606cdce7d7398d03758b 100644 --- a/api/src/engine/engine.module.ts +++ b/api/src/engine/engine.module.ts @@ -1,73 +1,44 @@ -import { HttpModule, HttpService } from '@nestjs/axios'; -import { - DynamicModule, - Global, - InternalServerErrorException, - Logger, - Module, -} from '@nestjs/common'; -import { ENGINE_MODULE_OPTIONS, ENGINE_SERVICE } from './engine.constants'; +import { HttpModule } from '@nestjs/axios'; +import { CacheModule, DynamicModule, Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ENGINE_MODULE_OPTIONS } from './engine.constants'; import { EngineController } from './engine.controller'; -import { IEngineOptions, IEngineService } from './engine.interfaces'; import { EngineResolver } from './engine.resolver'; +import EngineService from './engine.service'; +import EngineOptions from './interfaces/engine-options.interface'; -@Global() @Module({}) export class EngineModule { - private static readonly logger = new Logger(EngineModule.name); - - static forRoot(options?: Partial<IEngineOptions>): DynamicModule { + static forRoot(options?: Partial<EngineOptions>): DynamicModule { const optionsProvider = { provide: ENGINE_MODULE_OPTIONS, useValue: { - type: process.env.ENGINE_TYPE, - baseurl: process.env.ENGINE_BASE_URL, - ...(options ?? {}), - }, - }; - - const engineProvider = { - provide: ENGINE_SERVICE, - useFactory: async (httpService: HttpService) => { - return this.createEngineConnection( - optionsProvider.useValue, - httpService, - ); + ...options, + type: options?.type.toLowerCase(), }, - inject: [HttpService], }; return { + global: true, module: EngineModule, - imports: [HttpModule], - providers: [optionsProvider, engineProvider, EngineResolver], + imports: [ + HttpModule, + CacheModule.registerAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => { + const config = configService.get('cache'); + return { + isGlobal: true, + ttl: config.ttl, + max: config.max, + }; + }, + inject: [ConfigService], + }), + ], + providers: [optionsProvider, EngineService, EngineResolver], controllers: [EngineController], - exports: [optionsProvider, engineProvider], + exports: [optionsProvider, EngineService], }; } - - private static async createEngineConnection( - opt: IEngineOptions, - httpService: HttpService, - ): Promise<IEngineService> { - const service = await import(`./connectors/${opt.type}/main.connector`); - const instance: IEngineService = new service.default(opt, httpService); - - if (instance.createExperiment && instance.runExperiment) - throw new InternalServerErrorException( - `Connector ${opt.type} should declare either createExperiment or runExperiment not both`, - ); - - if ( - instance.createExperiment && - (!instance.getExperiment || - !instance.listExperiments || - !instance.removeExperiment || - !instance.editExperiment) - ) - throw new InternalServerErrorException( - `Connector ${opt.type} has 'createExperiment' implemented it implies that getExperiment, listExperiments, removeExperiment and editExperiment methods must also be implemented.`, - ); - return instance; - } } diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts index 84ff2afbb7f04f97af6072d29fe25fd7d380079c..f6ecef04897d77c9bef9488eabc74bfc484e83e1 100644 --- a/api/src/engine/engine.resolver.ts +++ b/api/src/engine/engine.resolver.ts @@ -11,11 +11,11 @@ import { GQLRequest } from '../common/decorators/gql-request.decoractor'; import { ENGINE_MODULE_OPTIONS, ENGINE_ONTOLOGY_URL, - ENGINE_SERVICE, ENGINE_SKIP_TOS, } from './engine.constants'; -import { IEngineOptions, IEngineService } from './engine.interfaces'; +import EngineService from './engine.service'; import { ErrorsInterceptor } from './interceptors/errors.interceptor'; +import EngineOptions from './interfaces/engine-options.interface'; import { Configuration } from './models/configuration.model'; import { Domain } from './models/domain.model'; import { Algorithm } from './models/experiment/algorithm.model'; @@ -27,16 +27,16 @@ import { FormulaOperation } from './models/formula/formula-operation.model'; @Resolver() export class EngineResolver { constructor( - @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, + private readonly engineService: EngineService, @Inject(ENGINE_MODULE_OPTIONS) - private readonly engineOptions: IEngineOptions, + private readonly engineOptions: EngineOptions, private readonly configSerivce: ConfigService, ) {} @Query(() => Configuration) @Public() configuration(): Configuration { - const config = this.engineService.getConfiguration?.(); + const config = this.engineService.getConfiguration(); const matomo = this.configSerivce.get('matomo'); const data = { @@ -78,7 +78,7 @@ export class EngineResolver { @Query(() => [FormulaOperation]) async formula() { - if (this.engineService.getFormulaConfiguration) + if (this.engineService.has('getFormulaConfiguration')) return this.engineService.getFormulaConfiguration(); return []; @@ -86,7 +86,7 @@ export class EngineResolver { @Query(() => FilterConfiguration) async filter() { - if (this.engineService.getFilterConfiguration) + if (this.engineService.has('getFilterConfiguration')) return this.engineService.getFilterConfiguration(); return []; diff --git a/api/src/engine/engine.service.ts b/api/src/engine/engine.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..bf6d88275051671bd2f246927f5555ea5c0d728d --- /dev/null +++ b/api/src/engine/engine.service.ts @@ -0,0 +1,213 @@ +import { HttpService } from '@nestjs/axios'; +import { + CACHE_MANAGER, + Inject, + Injectable, + InternalServerErrorException, + NotImplementedException, +} from '@nestjs/common'; +import { ConfigType } from '@nestjs/config'; +import { Cache } from 'cache-manager'; +import { Request } from 'express'; +import { Observable } from 'rxjs'; +import { ExperimentResult } from '../common/interfaces/utilities.interface'; +import cacheConfig from '../config/cache.config'; +import { ExperimentCreateInput } from '../experiments/models/input/experiment-create.input'; +import { ExperimentEditInput } from '../experiments/models/input/experiment-edit.input'; +import { UpdateUserInput } from '../users/inputs/update-user.input'; +import { User } from '../users/models/user.model'; +import { ENGINE_MODULE_OPTIONS } from './engine.constants'; +import ConnectorConfiguration from './interfaces/connector-configuration.interface'; +import Connector from './interfaces/connector.interface'; +import EngineOptions from './interfaces/engine-options.interface'; +import { Domain } from './models/domain.model'; +import { Algorithm } from './models/experiment/algorithm.model'; +import { + Experiment, + PartialExperiment, +} from './models/experiment/experiment.model'; +import { ListExperiments } from './models/experiment/list-experiments.model'; +import { FilterConfiguration } from './models/filter/filter-configuration'; +import { FormulaOperation } from './models/formula/formula-operation.model'; + +/** + * Engine service. + * This class is used as a Proxy to the real Connector. + */ +@Injectable() +export default class EngineService implements Connector { + private connector: Connector; + + constructor( + @Inject(ENGINE_MODULE_OPTIONS) private readonly options: EngineOptions, + private readonly httpService: HttpService, + @Inject(CACHE_MANAGER) private cacheManager: Cache, + @Inject(cacheConfig.KEY) private cacheConf: ConfigType<typeof cacheConfig>, + ) { + import(`./connectors/${options.type}/${options.type}.connector`).then( + (conn) => { + const instance = new conn.default(options, httpService); + + if (instance.createExperiment && instance.runExperiment) + throw new InternalServerErrorException( + `Connector ${options.type} should declare either createExperiment or runExperiment not both`, + ); + + if ( + instance.createExperiment && + (!instance.getExperiment || + !instance.listExperiments || + !instance.removeExperiment || + !instance.editExperiment) + ) + throw new InternalServerErrorException( + `Connector ${options.type} has 'createExperiment' implemented it implies that getExperiment, listExperiments, removeExperiment and editExperiment methods must also be implemented.`, + ); + + this.connector = instance; + }, + ); + } + + getConfiguration(): ConnectorConfiguration { + return this.connector.getConfiguration?.() ?? {}; + } + + /** + * "If the cache is enabled, try to get the value from the cache, otherwise call the function and cache + * the result." + * + * The function takes two arguments: + * + * * `key`: The key to use for the cache. + * * `fn`: The function to call if the value is not in the cache + * @param {string} key - The key to use for the cache. + * @param fn - () => Promise<T> + * @returns The result of the function call. + */ + private async getFromCacheOrCall<T>( + key: string, + fn: () => Promise<T>, + ): Promise<T | undefined> { + if (!key || !this.cacheConf.enabled) return fn(); + + const cached = await this.cacheManager.get<T>(key); + if (cached) return cached; + + const result = await fn(); + + this.cacheManager.set(key, result); + + return result; + } + + async getDomains(ids: string[], req: Request): Promise<Domain[]> { + const user = req?.user as User; + const key = user.id ? `domains-${ids.join('-')}-${user.id}` : undefined; + + return this.getFromCacheOrCall<Domain[]>(key, () => + this.connector.getDomains(ids, req), + ); + } + + async getAlgorithms(req: Request): Promise<Algorithm[]> { + const key = 'algorithms'; + + return this.getFromCacheOrCall<Algorithm[]>(key, () => + this.connector.getAlgorithms(req), + ); + } + + async createExperiment( + data: ExperimentCreateInput, + isTransient: boolean, + req?: Request, + ): Promise<Experiment> { + if (!this.connector.createExperiment) throw new NotImplementedException(); + return this.connector.createExperiment(data, isTransient, req); + } + + async runExperiment( + data: ExperimentCreateInput, + req?: Request, + ): Promise<ExperimentResult[]> { + if (!this.connector.runExperiment) throw new NotImplementedException(); + return this.connector.runExperiment(data, req); + } + + async listExperiments?( + page: number, + name: string, + req?: Request, + ): Promise<ListExperiments> { + if (!this.connector.listExperiments) throw new NotImplementedException(); + return this.connector.listExperiments(page, name, req); + } + + async getExperiment?(id: string, req?: Request): Promise<Experiment> { + if (!this.connector.getExperiment) throw new NotImplementedException(); + return this.connector.getExperiment(id, req); + } + + async removeExperiment?( + id: string, + req?: Request, + ): Promise<PartialExperiment> { + if (!this.connector.removeExperiment) throw new NotImplementedException(); + return this.connector.removeExperiment(id, req); + } + + async editExperiment?( + id: string, + data: ExperimentEditInput, + req?: Request, + ): Promise<Experiment> { + if (!this.connector.editExperiment) throw new NotImplementedException(); + return this.connector.editExperiment(id, data, req); + } + + async getActiveUser?(req?: Request): Promise<User> { + if (!this.connector.getActiveUser) throw new NotImplementedException(); + return this.connector.getActiveUser(req); + } + + async updateUser?( + req?: Request, + userId?: string, + data?: UpdateUserInput, + ): Promise<User> { + if (!this.connector.updateUser) throw new NotImplementedException(); + return this.connector.updateUser(req, userId, data); + } + + async getFormulaConfiguration?(req?: Request): Promise<FormulaOperation[]> { + if (!this.connector.getFormulaConfiguration) + throw new NotImplementedException(); + return this.connector.getFormulaConfiguration(req); + } + + async getFilterConfiguration?(req?: Request): Promise<FilterConfiguration[]> { + if (!this.connector.getFilterConfiguration) + throw new NotImplementedException(); + return this.connector.getFilterConfiguration(req); + } + + async logout?(req?: Request): Promise<void> { + if (!this.connector.logout) throw new NotImplementedException(); + return this.connector.logout(req); + } + + async login?(username: string, password: string): Promise<User> { + if (!this.connector.login) throw new NotImplementedException(); + return this.connector.login(username, password); + } + + getPassthrough?(suffix: string, req?: Request): string | Observable<string> { + if (!this.connector.getPassthrough) throw new NotImplementedException(); + return this.connector.getPassthrough(suffix, req); + } + + has(name: keyof Connector): boolean { + return this.connector && typeof this.connector[name] !== undefined; + } +} diff --git a/api/src/engine/interceptors/errors.interceptor.ts b/api/src/engine/interceptors/errors.interceptor.ts index 17bd2202b5ee03a82ba2c15bb2563da16f57a4ba..fa68a283ca34b0dee868c9c09e143d3c01a0109d 100644 --- a/api/src/engine/interceptors/errors.interceptor.ts +++ b/api/src/engine/interceptors/errors.interceptor.ts @@ -9,14 +9,14 @@ import { import { GqlExecutionContext } from '@nestjs/graphql'; import { catchError, Observable } from 'rxjs'; import { ENGINE_MODULE_OPTIONS } from '../engine.constants'; -import { IEngineOptions } from '../engine.interfaces'; +import EngineOptions from '../interfaces/engine-options.interface'; @Injectable() export class ErrorsInterceptor implements NestInterceptor { private readonly logger: Logger; constructor( - @Inject(ENGINE_MODULE_OPTIONS) private readonly options: IEngineOptions, + @Inject(ENGINE_MODULE_OPTIONS) private readonly options: EngineOptions, ) { // Logger name is the engine name // HttpService will be used mostly by the engine (but it's not always true) diff --git a/api/src/engine/interfaces/connector-configuration.interface.ts b/api/src/engine/interfaces/connector-configuration.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..d327ded434b169bd7c03ceda845198e0bd033cf6 --- /dev/null +++ b/api/src/engine/interfaces/connector-configuration.interface.ts @@ -0,0 +1,8 @@ +import { Configuration } from '../models/configuration.model'; + +type ConnectorConfiguration = Pick< + Configuration, + 'contactLink' | 'hasGalaxy' | 'hasGrouping' +>; + +export default ConnectorConfiguration; diff --git a/api/src/engine/engine.interfaces.ts b/api/src/engine/interfaces/connector.interface.ts similarity index 79% rename from api/src/engine/engine.interfaces.ts rename to api/src/engine/interfaces/connector.interface.ts index 5edaca4541165f02ea1d74ae38aa1407c8a2f49a..dc92dc71333914ca62d12a2b38da560848eb99a2 100644 --- a/api/src/engine/engine.interfaces.ts +++ b/api/src/engine/interfaces/connector.interface.ts @@ -1,43 +1,33 @@ import { Request } from 'express'; import { Observable } from 'rxjs'; +import { ExperimentResult } from 'src/common/interfaces/utilities.interface'; import { UpdateUserInput } from 'src/users/inputs/update-user.input'; -import { User } from '../users/models/user.model'; -import { Configuration } from './models/configuration.model'; -import { Domain } from './models/domain.model'; -import { Algorithm } from './models/experiment/algorithm.model'; +import { ExperimentCreateInput } from '../../experiments/models/input/experiment-create.input'; +import { ExperimentEditInput } from '../../experiments/models/input/experiment-edit.input'; +import { User } from '../../users/models/user.model'; +import { Domain } from '../models/domain.model'; +import { Algorithm } from '../models/experiment/algorithm.model'; import { Experiment, PartialExperiment, -} from './models/experiment/experiment.model'; -import { ExperimentCreateInput } from '../experiments/models/input/experiment-create.input'; -import { ExperimentEditInput } from '../experiments/models/input/experiment-edit.input'; -import { ListExperiments } from './models/experiment/list-experiments.model'; -import { ResultUnion } from './models/result/common/result-union.model'; -import { FormulaOperation } from './models/formula/formula-operation.model'; -import { FilterConfiguration } from './models/filter/filter-configuration'; - -export interface IEngineOptions { - type: string; - baseurl: string; -} - -export type IConfiguration = Pick< - Configuration, - 'contactLink' | 'hasGalaxy' | 'hasGrouping' ->; +} from '../models/experiment/experiment.model'; +import { ListExperiments } from '../models/experiment/list-experiments.model'; +import { FilterConfiguration } from '../models/filter/filter-configuration'; +import { FormulaOperation } from '../models/formula/formula-operation.model'; +import ConnectorConfiguration from './connector-configuration.interface'; -export interface IEngineService { +export default interface Connector { /** * Allow specific configuration for the engine */ - getConfiguration?(): IConfiguration; + getConfiguration?(): ConnectorConfiguration; /** * Get the list of domains along with a list of variables * @param ids - Ids to filter the domain needed * @param req - Request - this is the request object from the HTTP request. */ - getDomains(ids: string[], req?: Request): Domain[] | Promise<Domain[]>; + getDomains(ids: string[], req?: Request): Promise<Domain[]>; /** * Create and return a full detailed experiment @@ -62,7 +52,7 @@ export interface IEngineService { runExperiment?( data: ExperimentCreateInput, req?: Request, - ): Promise<Array<typeof ResultUnion>>; + ): Promise<ExperimentResult[]>; /** * Get a list of experiment (limited to 10 per page) diff --git a/api/src/engine/interfaces/engine-options.interface.ts b/api/src/engine/interfaces/engine-options.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..c05d4a0c3fe4bab5f9e9777cfba48ab928675edf --- /dev/null +++ b/api/src/engine/interfaces/engine-options.interface.ts @@ -0,0 +1,4 @@ +export default interface EngineOptions { + type: string; + baseurl: string; +} diff --git a/api/src/engine/test/core.e2e-spec.ts b/api/src/engine/test/core.e2e-spec.ts index bb35467dd1911ea2f88c8af9f0268218ddfdb8a5..f76bd806049be71cb7d78e6f53ca915e0b0bac52 100644 --- a/api/src/engine/test/core.e2e-spec.ts +++ b/api/src/engine/test/core.e2e-spec.ts @@ -1,14 +1,13 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { Domain } from 'src/engine/models/domain.model'; +import { Domain } from '../models/domain.model'; import { AppModule } from '../../main/app.module'; import { TIMEOUT_DURATION_SECONDS } from '../connectors/exareme/interfaces/test-utilities'; -import { ENGINE_SERVICE } from '../engine.constants'; -import { IEngineService } from '../engine.interfaces'; +import EngineService from '../engine.service'; jest.setTimeout(1000 * TIMEOUT_DURATION_SECONDS); describe('Engine service', () => { - let engineService: IEngineService; + let engineService: EngineService; let domains: Domain[]; beforeAll(async () => { @@ -16,7 +15,7 @@ describe('Engine service', () => { imports: [AppModule], }).compile(); - engineService = await moduleRef.resolve<IEngineService>(ENGINE_SERVICE); + engineService = await moduleRef.resolve<EngineService>(EngineService); domains = await engineService.getDomains([]); }); diff --git a/api/src/experiments/experiments.resolver.spec.ts b/api/src/experiments/experiments.resolver.spec.ts index 0ff1cb091669713156af236d4b504706a29074a1..68907635d623d9a6b49026d5c8387490670143ba 100644 --- a/api/src/experiments/experiments.resolver.spec.ts +++ b/api/src/experiments/experiments.resolver.spec.ts @@ -1,14 +1,13 @@ import { Test, TestingModule } from '@nestjs/testing'; +import EngineService from '../engine/engine.service'; import { ExperimentStatus } from '../engine/models/experiment/experiment.model'; import { User } from '../users/models/user.model'; -import { ENGINE_SERVICE } from '../engine/engine.constants'; -import { IEngineService } from '../engine/engine.interfaces'; import { ExperimentsResolver } from './experiments.resolver'; import { ExperimentsService } from './experiments.service'; import { ExperimentCreateInput } from './models/input/experiment-create.input'; import { ExperimentEditInput } from './models/input/experiment-edit.input'; -type MockEngineService = Partial<Record<keyof IEngineService, jest.Mock>>; +type MockEngineService = Partial<Record<keyof EngineService, jest.Mock>>; type MockExperimentService = Partial< Record<keyof ExperimentsService, jest.Mock> >; @@ -22,6 +21,7 @@ const createEngineService = (): MockEngineService => ({ editExperiment: jest.fn(), listExperiments: jest.fn(), removeExperiment: jest.fn(), + has: jest.fn().mockReturnValue(true), }); const createExperimentsService = (): MockExperimentService => ({ @@ -44,13 +44,15 @@ describe('ExperimentsResolver', () => { ExperimentsResolver, { provide: ExperimentsService, useValue: createExperimentsService() }, { - provide: ENGINE_SERVICE, + provide: EngineService, useValue: createEngineService(), }, ], }).compile(); - engineService = module.get<MockEngineService>(ENGINE_SERVICE); + engineService = module.get<EngineService>( + EngineService, + ) as unknown as MockEngineService; experimentsService = module.get<ExperimentsService>( ExperimentsService, ) as unknown as MockExperimentService; @@ -77,7 +79,7 @@ describe('ExperimentsResolver', () => { describe('when engine method does not exist', () => { it('should call service method', async () => { const request: any = jest.fn(); - engineService.listExperiments = undefined; + engineService.has.mockReturnValue(false); experimentsService.findAll.mockReturnValue([[], 9]); await resolver.experimentList(0, '', request); @@ -110,7 +112,7 @@ describe('ExperimentsResolver', () => { id: 'dummyUser', username: 'test', }; - engineService.getExperiment = undefined; + engineService.has.mockReturnValue(false); await resolver.experiment('test', request, user); expect(experimentsService.findOne.mock.calls.length).toBeGreaterThan(0); @@ -146,7 +148,7 @@ describe('ExperimentsResolver', () => { id: 'dummyUser', username: 'test', }; - engineService.createExperiment = undefined; + engineService.has.mockReturnValue(false); engineService.runExperiment.mockResolvedValue([]); experimentsService.create.mockReturnValue({ id: 'test' }); await resolver.createExperiment(request, user, data, false); @@ -162,7 +164,7 @@ describe('ExperimentsResolver', () => { id: 'dummyUser', username: 'test', }; - engineService.createExperiment = undefined; + engineService.has.mockReturnValue(false); engineService.runExperiment.mockResolvedValue([]); experimentsService.create.mockReturnValue({ id: 'test' }); const result = await resolver.createExperiment( @@ -206,7 +208,7 @@ describe('ExperimentsResolver', () => { id: 'dummyUser', username: 'test', }; - engineService.editExperiment = undefined; + engineService.has.mockReturnValue(false); await resolver.editExperiment(request, 'test', data, user); expect(experimentsService.update.mock.calls.length).toBeGreaterThan(0); @@ -238,7 +240,7 @@ describe('ExperimentsResolver', () => { id: 'dummyUser', username: 'test', }; - engineService.removeExperiment = undefined; + engineService.has.mockReturnValue(false); await resolver.removeExperiment('test', request, user); expect(experimentsService.remove.mock.calls.length).toBeGreaterThan(0); diff --git a/api/src/experiments/experiments.resolver.ts b/api/src/experiments/experiments.resolver.ts index 29b15fbe6aad521b235f5e7076caa6d2e9b17303..3739933dfa2c2b736f4851375afc0f272e456b3c 100644 --- a/api/src/experiments/experiments.resolver.ts +++ b/api/src/experiments/experiments.resolver.ts @@ -1,11 +1,10 @@ -import { Inject, Logger, UseGuards } from '@nestjs/common'; +import { Logger, UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; import { Request } from 'express'; +import EngineService from '../engine/engine.service'; import { GlobalAuthGuard } from '../auth/guards/global-auth.guard'; import { GQLRequest } from '../common/decorators/gql-request.decoractor'; import { CurrentUser } from '../common/decorators/user.decorator'; -import { ENGINE_SERVICE } from '../engine/engine.constants'; -import { IEngineService } from '../engine/engine.interfaces'; import { Experiment, ExperimentStatus, @@ -25,7 +24,7 @@ export class ExperimentsResolver { private readonly logger = new Logger(ExperimentsResolver.name); constructor( - @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, + private readonly engineService: EngineService, private readonly experimentService: ExperimentsService, ) {} @@ -35,7 +34,7 @@ export class ExperimentsResolver { @Args('name', { nullable: true, defaultValue: '' }) name: string, @GQLRequest() req: Request, ): Promise<ListExperiments> { - if (this.engineService.listExperiments) { + if (this.engineService.has('listExperiments')) { return this.engineService.listExperiments(page, name, req); } @@ -60,7 +59,7 @@ export class ExperimentsResolver { @GQLRequest() req: Request, @CurrentUser() user: User, ) { - if (this.engineService.getExperiment) + if (this.engineService.has('getExperiment')) return this.engineService.getExperiment(id, req); return this.experimentService.findOne(id, user); @@ -74,7 +73,7 @@ export class ExperimentsResolver { @Args('isTransient', { nullable: true, defaultValue: false }) isTransient: boolean, ) { - if (this.engineService.createExperiment) { + if (this.engineService.has('createExperiment')) { return this.engineService.createExperiment(data, isTransient, req); } @@ -116,7 +115,7 @@ export class ExperimentsResolver { @Args('data') experiment: ExperimentEditInput, @CurrentUser() user: User, ) { - if (this.engineService.editExperiment) + if (this.engineService.has('editExperiment')) return this.engineService.editExperiment(id, experiment, req); return this.experimentService.update(id, experiment, user); @@ -128,7 +127,7 @@ export class ExperimentsResolver { @GQLRequest() req: Request, @CurrentUser() user: User, ): Promise<PartialExperiment> { - if (this.engineService.removeExperiment) + if (this.engineService.has('removeExperiment')) return this.engineService.removeExperiment(id, req); return this.experimentService.remove(id, user); diff --git a/api/src/files/files.service.ts b/api/src/files/files.service.ts index 685ab86da64b8f1ecf1ead4f11a4e8fe5701ff5d..88638a18e416889b83cb251dbd7c2ddeed31b16c 100644 --- a/api/src/files/files.service.ts +++ b/api/src/files/files.service.ts @@ -1,14 +1,14 @@ import { Inject, Injectable } from '@nestjs/common'; import * as fs from 'fs'; import { join } from 'path/posix'; +import EngineOptions from '../engine/interfaces/engine-options.interface'; import { ENGINE_MODULE_OPTIONS } from '../engine/engine.constants'; -import { IEngineOptions } from '../engine/engine.interfaces'; @Injectable() export class FilesService { constructor( @Inject(ENGINE_MODULE_OPTIONS) - private readonly engineOptions: IEngineOptions, + private readonly engineOptions: EngineOptions, ) {} /** diff --git a/api/src/main/app.module.ts b/api/src/main/app.module.ts index dd03343d458c661bc3aacd67615c17bcda10beab..5c113466f7233d9ff30419b48c9c495e60ae9f66 100644 --- a/api/src/main/app.module.ts +++ b/api/src/main/app.module.ts @@ -6,6 +6,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { GraphQLError } from 'graphql'; import { join } from 'path'; import { AuthModule } from 'src/auth/auth.module'; +import cacheConfig from 'src/config/cache.config'; import dbConfig from 'src/config/db.config'; import matomoConfig from 'src/config/matomo.config'; import { EngineModule } from 'src/engine/engine.module'; @@ -20,7 +21,7 @@ import { AppService } from './app.service'; ConfigModule.forRoot({ isGlobal: true, envFilePath: ['.env', '.env.defaults'], - load: [dbConfig, matomoConfig], + load: [dbConfig, matomoConfig, cacheConfig], }), GraphQLModule.forRoot<ApolloDriverConfig>({ driver: ApolloDriver, diff --git a/api/src/users/users.resolver.spec.ts b/api/src/users/users.resolver.spec.ts index ad93fc2b885aaf71979c02a0f9a70cb6d6635d17..6f8fe9d584c1ea856cbc22c2090d899161c0a65a 100644 --- a/api/src/users/users.resolver.spec.ts +++ b/api/src/users/users.resolver.spec.ts @@ -1,18 +1,18 @@ import { getMockReq } from '@jest-mock/express'; import { InternalServerErrorException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; -import { ENGINE_SERVICE } from '../engine/engine.constants'; -import { IEngineService } from '../engine/engine.interfaces'; +import EngineService from '../engine/engine.service'; import { UpdateUserInput } from './inputs/update-user.input'; import { User } from './models/user.model'; import { UsersResolver } from './users.resolver'; import { UsersService } from './users.service'; -type MockEngineService = Partial<Record<keyof IEngineService, jest.Mock>>; +type MockEngineService = Partial<Record<keyof EngineService, jest.Mock>>; type MockUsersService = Partial<Record<keyof UsersService, jest.Mock>>; const createEngineService = (): MockEngineService => ({ updateUser: jest.fn(), + has: jest.fn().mockReturnValue(true), }); const createUsersService = (): MockUsersService => ({ @@ -37,14 +37,16 @@ describe('UsersResolver', () => { UsersResolver, { provide: UsersService, useValue: createUsersService() }, { - provide: ENGINE_SERVICE, + provide: EngineService, useValue: createEngineService(), }, ], }).compile(); resolver = module.get<UsersResolver>(UsersResolver); - engineService = module.get<MockEngineService>(ENGINE_SERVICE); + engineService = module.get<EngineService>( + EngineService, + ) as unknown as MockEngineService; usersService = module.get<UsersService>( UsersService, ) as unknown as MockUsersService; @@ -95,7 +97,7 @@ describe('UsersResolver', () => { ...updateData, }; - engineService.updateUser = undefined; + engineService.has.mockReturnValue(false); usersService.update.mockReturnValue(expectedUser); const result = await resolver.updateUser(req, updateData, user); diff --git a/api/src/users/users.resolver.ts b/api/src/users/users.resolver.ts index 5d54d88d854d5e34787dd893ee36987bf348f5ea..d13ae670e9499d07b768cd3706ff1173f3de4135 100644 --- a/api/src/users/users.resolver.ts +++ b/api/src/users/users.resolver.ts @@ -1,19 +1,17 @@ import { - Inject, InternalServerErrorException, Logger, UseGuards, } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; -import { ENGINE_SERVICE } from '../engine/engine.constants'; -import { IEngineService } from '../engine/engine.interfaces'; +import { Request } from 'express'; +import { GlobalAuthGuard } from '../auth/guards/global-auth.guard'; +import { GQLRequest } from '../common/decorators/gql-request.decoractor'; +import { CurrentUser } from '../common/decorators/user.decorator'; +import EngineService from '../engine/engine.service'; import { UpdateUserInput } from './inputs/update-user.input'; import { User } from './models/user.model'; import { UsersService } from './users.service'; -import { GQLRequest } from '../common/decorators/gql-request.decoractor'; -import { Request } from 'express'; -import { CurrentUser } from '../common/decorators/user.decorator'; -import { GlobalAuthGuard } from '../auth/guards/global-auth.guard'; @UseGuards(GlobalAuthGuard) @Resolver() @@ -22,7 +20,7 @@ export class UsersResolver { constructor( private readonly usersService: UsersService, - @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, + private readonly engineService: EngineService, ) {} @Query(() => User, { name: 'user' }) @@ -56,7 +54,7 @@ export class UsersResolver { let updatedInfo: Partial<User>; - if (this.engineService.updateUser) { + if (this.engineService.has('updateUser')) { updatedInfo = await this.engineService.updateUser( request, user?.id, diff --git a/api/tsconfig.json b/api/tsconfig.json index adb614cab7d04d6f2391c7663b833b296ed334e1..0903174b9e77d1331521c7bc2db68acfd4217691 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -6,7 +6,7 @@ "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "es2017", + "target": "es2021", "sourceMap": true, "outDir": "./dist", "baseUrl": "./",