diff --git a/api/src/engine/connectors/datashield/datashield.connector.ts b/api/src/engine/connectors/datashield/datashield.connector.ts index 244c56621dd6d018f73655df9fec142416934e21..62249ee5dadccdae77ab9c07d9a6e6ab15fdd8e5 100644 --- a/api/src/engine/connectors/datashield/datashield.connector.ts +++ b/api/src/engine/connectors/datashield/datashield.connector.ts @@ -1,5 +1,5 @@ import { HttpService } from '@nestjs/axios'; -import { Inject, InternalServerErrorException, Logger } from '@nestjs/common'; +import { InternalServerErrorException, Logger } from '@nestjs/common'; import { Request } from 'express'; import { catchError, firstValueFrom } from 'rxjs'; import { @@ -7,7 +7,6 @@ import { MIME_TYPES, } from 'src/common/interfaces/utilities.interface'; import { errorAxiosHandler } from 'src/common/utils/shared.utils'; -import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; import EngineService from 'src/engine/engine.service'; import ConnectorConfiguration from 'src/engine/interfaces/connector-configuration.interface'; import Connector from 'src/engine/interfaces/connector.interface'; @@ -38,7 +37,7 @@ export default class DataShieldConnector implements Connector { private static readonly logger = new Logger(DataShieldConnector.name); headers = {}; constructor( - @Inject(ENGINE_MODULE_OPTIONS) private readonly options: EngineOptions, + private readonly options: EngineOptions, private readonly httpService: HttpService, private readonly engineService: EngineService, ) {} diff --git a/api/src/engine/connectors/exareme/converters.ts b/api/src/engine/connectors/exareme/converters.ts index 86b58070bbca5bfcada6303b2ec4806b6df9ced8..a9a165184c4cd7c11103b1e1ab156354e91bddd3 100644 --- a/api/src/engine/connectors/exareme/converters.ts +++ b/api/src/engine/connectors/exareme/converters.ts @@ -1,31 +1,28 @@ +import { Logger } from '@nestjs/common'; import { MIME_TYPES } from 'src/common/interfaces/utilities.interface'; import { Category } from 'src/engine/models/category.model'; import { Dataset } from 'src/engine/models/dataset.model'; +import { Domain } from 'src/engine/models/domain.model'; import { Experiment, ExperimentStatus, } from 'src/engine/models/experiment/experiment.model'; import { Group } from 'src/engine/models/group.model'; -import { ResultUnion } from 'src/engine/models/result/common/result-union.model'; import { GroupResult, GroupsResult, } from 'src/engine/models/result/groups-result.model'; -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 handlers from './handlers'; import { Entity } from './interfaces/entity.interface'; import { ExperimentData } from './interfaces/experiment/experiment.interface'; -import { ResultChartExperiment } from './interfaces/experiment/result-chart-experiment.interface'; import { ResultExperiment } from './interfaces/experiment/result-experiment.interface'; import { Hierarchy } from './interfaces/hierarchy.interface'; import { VariableEntity } from './interfaces/variable-entity.interface'; import { - dataROCToLineResult, - dataToHeatmap, descriptiveModelToTables, descriptiveSingleToTables, transformToExperiment, @@ -80,7 +77,10 @@ const algoParamInputToData = (param: AlgorithmParamInput) => { }; }; -export const experimentInputToData = (data: ExperimentCreateInput) => { +export const experimentInputToData = ( + data: ExperimentCreateInput, + domains?: Domain[], +) => { const formula = ((data.transformations?.length > 0 || data.interactions?.length > 0) && { single: @@ -95,6 +95,8 @@ export const experimentInputToData = (data: ExperimentCreateInput) => { }) || null; + const domain = domains?.find((d) => d.id === data.domain); + const params = { algorithm: { parameters: [ @@ -106,12 +108,14 @@ export const experimentInputToData = (data: ExperimentCreateInput) => { { name: 'filter', label: 'filter', - value: data.filter, + value: data.filter ?? '', }, { name: 'pathology', label: 'pathology', - value: data.domain, + value: domain.version + ? `${data.domain}:${domain.version}` + : data.domain, }, ...(formula ? [ @@ -205,6 +209,8 @@ export const descriptiveDataToTableResult = ( export const dataToExperiment = ( data: ExperimentData, + logger: Logger, + domains?: Domain[], ): Experiment | undefined => { try { const expTransform = transformToExperiment.evaluate(data); @@ -214,12 +220,10 @@ export const dataToExperiment = ( results: [], }; - exp.results = data.result - ? data.result - .map((result) => dataToResult(result, exp.algorithm.name)) - .filter((r) => r.length > 0) - .flat() - : []; + const domain = domains?.find((d) => d.id === exp.domain); + + if (data && data.result && data.result.length) + handlers(exp, data.result, domain); const allVariables = exp.filterVariables || []; @@ -242,6 +246,8 @@ export const dataToExperiment = ( return exp; } catch (e) { + logger.error('Error parsing experiment', data.uuid); + logger.debug(e); return { id: data.uuid, name: data.name, @@ -280,46 +286,3 @@ export const dataToRaw = ( }, ]; }; - -export const dataToResult = ( - result: ResultExperiment, - algo: string, -): Array<typeof ResultUnion> => { - switch (result.type.toLowerCase()) { - case 'application/json': - return dataJSONtoResult(result, algo); - case 'application/vnd.highcharts+json': - return dataHighchartToResult(result as ResultChartExperiment, algo); - default: - return dataToRaw(algo, result); - } -}; - -export const dataJSONtoResult = ( - result: ResultExperiment, - algo: string, -): Array<typeof ResultUnion> => { - switch (algo.toLowerCase()) { - case 'cart': - case 'id3': - return dataToRaw(algo, result); - case 'descriptive_stats': - return descriptiveDataToTableResult(result); - default: - return []; - } -}; - -export const dataHighchartToResult = ( - result: ResultChartExperiment, - algo: string, -): Array<typeof ResultUnion> => { - switch (result.data.chart.type) { - case 'heatmap': - return [dataToHeatmap.evaluate(result) as HeatMapResult]; - case 'area': - return [dataROCToLineResult.evaluate(result) as LineChartResult]; - default: - return dataToRaw(algo, result); - } -}; diff --git a/api/src/engine/connectors/exareme/exareme.connector.ts b/api/src/engine/connectors/exareme/exareme.connector.ts index 8fd464d61c773e7061bee76e8c1271fad324cfa6..51b4c3f77417689c32ee879c5d1c63ae9c9001c8 100644 --- a/api/src/engine/connectors/exareme/exareme.connector.ts +++ b/api/src/engine/connectors/exareme/exareme.connector.ts @@ -3,14 +3,14 @@ import { BadRequestException, HttpException, HttpStatus, - Inject, Injectable, InternalServerErrorException, + Logger, } from '@nestjs/common'; import { AxiosRequestConfig } from 'axios'; import { Request } from 'express'; import { firstValueFrom, map, Observable } from 'rxjs'; -import { ENGINE_MODULE_OPTIONS } from 'src/engine/engine.constants'; +import EngineService from 'src/engine/engine.service'; 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'; @@ -46,9 +46,12 @@ type Headers = Record<string, string>; @Injectable() export default class ExaremeConnector implements Connector { + private static readonly logger = new Logger(ExaremeConnector.name); + constructor( - @Inject(ENGINE_MODULE_OPTIONS) private readonly options: EngineOptions, + private readonly options: EngineOptions, private readonly httpService: HttpService, + private readonly engineService: EngineService, ) {} async getFormulaConfiguration(): Promise<FormulaOperation[]> { @@ -82,7 +85,9 @@ export default class ExaremeConnector implements Connector { isTransient = false, request: Request, ): Promise<Experiment> { - const form = experimentInputToData(data); + const domains = await this.engineService.getDomains(request); + + const form = experimentInputToData(data, domains); const path = this.options.baseurl + `experiments${isTransient ? '/transient' : ''}`; @@ -91,7 +96,7 @@ export default class ExaremeConnector implements Connector { this.post<ExperimentData>(request, path, form), ); - return dataToExperiment(resultAPI.data); + return dataToExperiment(resultAPI.data, ExaremeConnector.logger, domains); } async listExperiments( @@ -109,7 +114,10 @@ export default class ExaremeConnector implements Connector { return { ...resultAPI.data, - experiments: resultAPI.data.experiments?.map(dataToExperiment) ?? [], + experiments: + resultAPI.data.experiments?.map((exp) => + dataToExperiment(exp, ExaremeConnector.logger), + ) ?? [], }; } @@ -120,6 +128,7 @@ export default class ExaremeConnector implements Connector { return transformToAlgorithms.evaluate(resultAPI.data); } + async getExperiment(id: string, request: Request): Promise<Experiment> { const path = this.options.baseurl + `experiments/${id}`; @@ -127,7 +136,9 @@ export default class ExaremeConnector implements Connector { this.get<ExperimentData>(request, path), ); - return dataToExperiment(resultAPI.data); + const domains = await this.engineService.getDomains(request); + + return dataToExperiment(resultAPI.data, ExaremeConnector.logger, domains); } async editExperiment( @@ -141,7 +152,9 @@ export default class ExaremeConnector implements Connector { this.patch<ExperimentData>(request, path, expriment), ); - return dataToExperiment(resultAPI.data); + const domains = await this.engineService.getDomains(request); + + return dataToExperiment(resultAPI.data, ExaremeConnector.logger, domains); } async removeExperiment( @@ -173,6 +186,7 @@ export default class ExaremeConnector implements Connector { return { id: d.code, label: d.label, + version: d.version, groups: groups, rootGroup: dataToGroup(d.metadataHierarchy), datasets: d.datasets ? d.datasets.map(dataToDataset) : [], 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 d2a299b68ae5de8399e3c4116548d2303a04e971..fd0a46d6d38f19e9dfac67bf3ec798431ff503a8 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 @@ -24,15 +24,15 @@ const createExperiment = (): Experiment => ({ results: [], }); -describe('PCA result handler', () => { - const data = { +const data = [ + { n_obs: 920, - eigen_vals: [ + eigenvalues: [ 4.355128575368841, 1.0450017532423244, 0.7421875389691931, 0.5621334410413102, 0.4449774285333259, 0.3926006044635884, 0.24709792171717856, 0.2195778509188677, ], - eigen_vecs: [ + eigenvectors: [ [ -0.39801260415007417, -0.408618968170694, -0.005388160172392957, -0.39520225162483835, -0.34101372698341037, -0.3857891852309699, @@ -74,27 +74,27 @@ describe('PCA result handler', () => { 0.29263999750747904, -0.009909769193655195, ], ], - }; + }, +]; +describe('PCA result handler', () => { it('Test PCA handler with regular data (no edge cases)', () => { const exp = createExperiment(); handlers(exp, data, null); expect(exp.results.length).toBeGreaterThanOrEqual(2); - exp.results.forEach((it) => { - if (it['matrix']) { - const heatmap = it as HeatMapResult; - const matrix = data.eigen_vecs[0].map( - (_, i) => data.eigen_vecs.map((row) => row[i]), // reverse matrix as we want row-major order + exp.results.forEach((result) => { + if (result['matrix']) { + const heatmap = result as HeatMapResult; + const matrix = data[0].eigenvectors[0].map( + (_, i) => data[0].eigenvectors.map((row) => row[i]), // reverse matrix as we want row-major order ); expect(heatmap.matrix).toEqual(matrix); expect(heatmap.yAxis.categories).toEqual(exp.variables); } - }); - exp.results.forEach((it) => { - if (it['barValues']) { - const barchart = it as BarChartResult; - expect(barchart.barValues).toEqual(data.eigen_vals); + if (result['barValues']) { + const barchart = result as BarChartResult; + expect(barchart.barValues).toEqual(data[0].eigenvalues); } }); }); diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts index 61f82be6593f6165326387e228971236679e1d84..32f08a02deb817ca2078c07774e39ac06a26f041 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/PCA.handler.ts @@ -1,20 +1,32 @@ +import { Domain } from 'src/engine/models/domain.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { BarChartResult } from '../../../../models/result/bar-chart-result.model'; -import { HeatMapResult } from '../../../../models/result/heat-map-result.model'; +import { + HeatMapResult, + HeatMapStyle, +} from '../../../../models/result/heat-map-result.model'; import BaseHandler from '../base.handler'; export default class PCAHandler extends BaseHandler { - canHandle(algorithm: string): boolean { - return algorithm.toLocaleLowerCase() === 'pca'; + canHandle(algorithm: string, data: any): boolean { + return ( + algorithm.toLowerCase() === 'pca' && + data && + data[0] && + data[0]['eigenvalues'] && + data[0]['eigenvectors'] + ); } - handle(exp: Experiment, data: unknown): void { - if (!this.canHandle(exp.algorithm.name)) - return this.next?.handle(exp, data); + handle(exp: Experiment, data: any, domain?: Domain): void { + if (!this.canHandle(exp.algorithm.name, data)) + return this.next?.handle(exp, data, domain); + + const extractedData = data[0]; const barChar: BarChartResult = { name: 'Eigen values', - barValues: data['eigen_vals'], + barValues: extractedData['eigenvalues'], xAxis: { label: 'Dimensions', categories: exp.variables.map((_, i) => i + 1).map(String), @@ -28,11 +40,12 @@ export default class PCAHandler extends BaseHandler { if (barChar.barValues && barChar.barValues.length > 0) exp.results.push(barChar); - const matrix = data['eigen_vecs'] as number[][]; + const matrix = extractedData['eigenvectors'] as number[][]; const heatMapChart: HeatMapResult = { name: 'Eigen vectors', matrix, + heatMapStyle: HeatMapStyle.BUBBLE, yAxis: { categories: exp.variables, }, 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 0de41ca99cd43c14bfb389c58ee28934dcb49f78..a251a54ec784afdb775ac0e7f8073bd96d2763d2 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 @@ -6,7 +6,7 @@ const createExperiment = (): Experiment => ({ id: 'dummy-id', name: 'Testing purpose', algorithm: { - name: 'Anova_OnEway', + name: 'one_way_ANOVA', }, datasets: ['desd-synthdata'], domain: 'dementia', @@ -15,19 +15,20 @@ const createExperiment = (): Experiment => ({ results: [], }); -describe('Anova oneway result handler', () => { - const anovaHandler = new AnovaOneWayHandler(); - const data = { - x_label: 'Variable X', - y_label: 'Variable Y', - df_residual: 1424.0, - df_explained: 3.0, - ss_residual: 1941.1517872154072, - ss_explained: 23.52938815624377, - ms_residual: 1.3631683898984601, - ms_explained: 7.843129385414589, - p_value: 0.0006542139533101455, - f_stat: 5.753602741623733, +const data = [ + { + anova_table: { + x_label: 'Variable X', + y_label: 'Variable Y', + df_residual: 1424.0, + df_explained: 3.0, + ss_residual: 1941.1517872154072, + ss_explained: 23.52938815624377, + ms_residual: 1.3631683898984601, + ms_explained: 7.843129385414589, + p_value: 0.0006542139533101455, + f_stat: 5.753602741623733, + }, tuckey_test: [ { groupA: 'GENPD', @@ -90,22 +91,11 @@ describe('Anova oneway result handler', () => { p_tuckey: 0.02355122851783331, }, ], - min_per_group: [ - { - GENPD: 7.2276, - HC: 7.2107, - PD: 7.0258, - PRODROMA: 6.3771, - }, - ], - max_per_group: [ - { - GENPD: 13.7312, - HC: 14.52, - PD: 14.4812, - PRODROMA: 12.3572, - }, - ], + min_max_per_group: { + categories: ['GENPD', 'HC, PD', 'PRODROMA'], + min: [2.6377, 8.4133, 2.6377, 8.4133], + max: [61.1428, 66.8592, 2.6377, 8.4133], + }, ci_info: { sample_stds: { GENPD: 1.2338388511229372, @@ -132,13 +122,17 @@ describe('Anova oneway result handler', () => { PRODROMA: 11.35849951898973, }, }, - }; + }, +]; + +describe('Anova oneway result handler', () => { + const anovaHandler = new AnovaOneWayHandler(); it('Test anova 1 way handler', () => { const exp = createExperiment(); - const table1 = anovaHandler.getSummaryTable(data, exp.coVariables[0]); - const table2 = anovaHandler.getTuckeyTable(data); - const meanPlot = anovaHandler.getMeanPlot(data); + const table1 = anovaHandler.getSummaryTable(data[0], exp.coVariables[0]); + const table2 = anovaHandler.getTuckeyTable(data[0]); + const meanPlot = anovaHandler.getMeanPlot(data[0]); handlers(exp, data, null); @@ -153,7 +147,7 @@ describe('Anova oneway result handler', () => { expect(meanPlot.pointCIs.length).toBeGreaterThan(1); expect(meanPlot.name).toEqual( - `Mean Plot: ${data.y_label} ~ ${data.x_label}`, + `Mean Plot: ${data[0].anova_table.y_label} ~ ${data[0].anova_table.x_label}`, ); }); }); 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 d52d088f9234d00d5aa06329b9d7d49e71a002a8..97880671b9bd5e1cacf3d86494ae1dc7df82a4a3 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 @@ -8,6 +8,8 @@ import { } from '../../../../models/result/table-result.model'; import BaseHandler from '../base.handler'; +const ALGO_NAME = 'one_way_anova'; + export default class AnovaOneWayHandler extends BaseHandler { private static readonly tuckeyTransform = jsonata(` { @@ -22,7 +24,7 @@ export default class AnovaOneWayHandler extends BaseHandler { {"name": 'T value', "type": 'string'}, {"name": 'P value', "type": 'string'} ], - "data": tuckey_test.[$.groupA, $.groupB, $.meanA, $.meanB, $.diff, $.se, $.t_stat, $.p_tuckey] + "data": tuckey_test.[$.groupA, $.groupB, $.meanA, $.meanB, $.diff, $.se, $.t_stat, $.p_tuckey][] } `); @@ -30,24 +32,24 @@ export default class AnovaOneWayHandler extends BaseHandler { ( $cats:= $keys(ci_info.means); { - "name": "Mean Plot: " & y_label & ' ~ ' & x_label, + "name": "Mean Plot: " & anova_table.y_label & ' ~ ' & anova_table.x_label, "xAxis": { - "label": x_label, + "label": anova_table.x_label, "categories": $cats }, "yAxis": { - "label": '95% CI: ' & y_label + "label": '95% CI: ' & anova_table.y_label }, - "pointCIs": $cats.[{ + "pointCIs": $cats.{ "min": $lookup($$.ci_info.'m-s', $), "mean": $lookup($$.ci_info.means, $), "max": $lookup($$.ci_info.'m+s', $) - }] + }[] }) `); canHandle(algorithm: string): boolean { - return algorithm.toLocaleLowerCase() === 'anova_oneway'; + return algorithm.toLocaleLowerCase() === ALGO_NAME; } getTuckeyTable(data: unknown): TableResult | undefined { @@ -57,7 +59,7 @@ export default class AnovaOneWayHandler extends BaseHandler { const tableResult: TableResult = { ...tableData, - tableStyle: TableStyle.NORMAL, + tableStyle: TableStyle.DEFAULT, } as unknown as TableResult; return tableResult; @@ -65,8 +67,8 @@ export default class AnovaOneWayHandler extends BaseHandler { getSummaryTable(data: unknown, varname: string): TableResult | undefined { const tableSummary: TableResult = { - name: 'Annova summary', - tableStyle: TableStyle.NORMAL, + name: 'Anova summary', + tableStyle: TableStyle.DEFAULT, headers: ['', 'DF', 'SS', 'MS', 'F ratio', 'P value'].map((name) => ({ name, type: 'string', @@ -74,17 +76,17 @@ export default class AnovaOneWayHandler extends BaseHandler { data: [ [ varname, - data['df_explained'], - data['ss_explained'], - data['ms_explained'], - data['p_value'], - data['f_stat'], + data['anova_table']['df_explained'], + data['anova_table']['ss_explained'], + data['anova_table']['ms_explained'], + data['anova_table']['p_value'], + data['anova_table']['f_stat'], ], [ 'Residual', - data['df_residual'], - data['ss_residual'], - data['ms_residual'], + data['anova_table']['df_residual'], + data['anova_table']['ss_residual'], + data['anova_table']['ms_residual'], '', '', ], @@ -94,23 +96,25 @@ export default class AnovaOneWayHandler extends BaseHandler { return tableSummary; } - getMeanPlot(data: unknown): MeanChartResult { + getMeanPlot(data: any): MeanChartResult { return AnovaOneWayHandler.meanPlotTransform.evaluate(data); } - handle(exp: Experiment, data: unknown, domain: Domain): void { + handle(exp: Experiment, data: any, domain: Domain): void { + if (!data || data.length === 0) return super.handle(exp, data, domain); + if (!this.canHandle(exp.algorithm.name)) return super.handle(exp, data, domain); - const summaryTable = this.getSummaryTable(data, exp.coVariables[0]); + const result = data[0]; + + const summaryTable = this.getSummaryTable(result, exp.coVariables[0]); if (summaryTable) exp.results.push(summaryTable); - const tuckeyTable = this.getTuckeyTable(data); + const tuckeyTable = this.getTuckeyTable(result); if (tuckeyTable) exp.results.push(tuckeyTable); - const meanPlot = this.getMeanPlot(data); + const meanPlot = this.getMeanPlot(result); if (meanPlot && meanPlot.pointCIs) exp.results.push(meanPlot); - - return super.handle(exp, data, domain); // continue request } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/descriptive.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/descriptive.handler.ts index 56396522d7a5504f218f586962d44cf81ac461b3..4e51ff16e39f652caa51c743ba08cfddd46c5ce3 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/descriptive.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/descriptive.handler.ts @@ -1,4 +1,5 @@ import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' +import { Domain } from '../../../../models/domain.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { GroupResult, @@ -37,6 +38,7 @@ $fn := function($o, $prefix) { $ks := $keys($model.*.data.*[$i][$type($) = 'object']); { 'name': $varName, + 'tableStyle': 1, 'headers': $append("", $keys($$.data.model)).{ 'name': $, 'type': 'string' @@ -66,6 +68,7 @@ $fn := function($o, $prefix) { $ks := $keys($p.*.data[$type($) = 'object']); { 'name': $keys(%)[$i], + 'tableStyle': 1, 'headers': $append("", $keys(*)).{ 'name': $, 'type': 'string' @@ -110,24 +113,17 @@ $fn := function($o, $prefix) { return result; } - handle(exp: Experiment, data: unknown): void { - let req = data; + handle(exp: Experiment, data: unknown, domain?: Domain): void { + if (exp.algorithm.name.toLowerCase() !== 'descriptive_stats') + return super.handle(exp, data, domain); - if (exp.algorithm.name.toLowerCase() === 'descriptive_stats') { - const inputs = data as ResultExperiment[]; + const inputs = data as ResultExperiment[]; - if (inputs && Array.isArray(inputs)) { - inputs - .filter((input) => input.type === 'application/json') - .map((input) => this.descriptiveDataToTableResult(input)) - .forEach((input) => exp.results.push(input)); - - req = JSON.stringify( - inputs.filter((input) => input.type !== 'application/json'), - ); - } + if (inputs && Array.isArray(inputs)) { + inputs + .filter((input) => input.type === 'application/json') + .map((input) => this.descriptiveDataToTableResult(input)) + .forEach((input) => exp.results.push(input)); } - - this.next?.handle(exp, req); } } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.spec.ts b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.spec.ts index f1a404cd7941f69101f2e59e641baedceefc6760..a2388797da8531010fbbd958421719dcdb8a87e1 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.spec.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.spec.ts @@ -3,19 +3,21 @@ import { TableResult } from 'src/engine/models/result/table-result.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import LinearRegressionCVHandler from './linear-regression-cv.handler'; -const data = { - dependent_var: 'leftocpoccipitalpole', - indep_vars: [ - 'Intercept', - 'righthippocampus', - 'rightsogsuperioroccipitalgyrus', - 'leftppplanumpolare', - ], - n_obs: [497, 498, 498, 499], - mean_sq_error: [0.3296455054532643, 0.02930654997949175], - r_squared: [0.5886631959286948, 0.04365853383949705], - mean_abs_error: [0.2585157369288272, 0.019919123005319055], -}; +const data = [ + { + dependent_var: 'leftocpoccipitalpole', + indep_vars: [ + 'Intercept', + 'righthippocampus', + 'rightsogsuperioroccipitalgyrus', + 'leftppplanumpolare', + ], + n_obs: [497, 498, 498, 499], + mean_sq_error: [0.3296455054532643, 0.02930654997949175], + r_squared: [0.5886631959286948, 0.04365853383949705], + mean_abs_error: [0.2585157369288272, 0.019919123005319055], + }, +]; const domain: Domain = { id: 'dummy-id', @@ -36,7 +38,7 @@ const createExperiment = (): Experiment => ({ id: 'dummy-id', name: 'Testing purpose', algorithm: { - name: 'LINEAR_REGRESSION_CROSS_VALIDATION', + name: 'LINEAR_REGRESSION_CV', }, datasets: ['desd-synthdata'], domain: 'dementia', @@ -76,14 +78,18 @@ describe('Linear regression CV result handler', () => { const json = JSON.stringify(experiment.results); + expect(experiment.results.length).toEqual(2); + const dataPoints = experiment.results[0] as TableResult; const scoresData = experiment.results[1] as TableResult; + console.log(JSON.stringify(dataPoints)); + console.log(JSON.stringify(scoresData)); + expect(dataPoints.data).toStrictEqual(expectedDataPoints); expect(scoresData.data).toStrictEqual(expectedScoresData); expect(json.includes(domain.variables[0].label)).toBeTruthy(); - expect(experiment.results.length === 2); }); it('Should be empty with another algo', () => { diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.ts index 89f46631872eb16e193e5fdd14c1d0371d4a5d96..4518157f624d6d2354fdb8189fb39c351dc614e8 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/linear-regression-cv.handler.ts @@ -9,7 +9,7 @@ import { import BaseHandler from '../base.handler'; const NUMBER_PRECISION = 4; -const ALGO_NAME = 'linear_regression_cross_validation'; +const ALGO_NAME = 'linear_regression_cv'; const lookupDict = { dependent_var: 'Dependent variable', indep_vars: 'Independent variables', @@ -23,7 +23,7 @@ export default class LinearRegressionCVHandler extends BaseHandler { private getModel(data: any): TableResult | undefined { return { name: 'Data points', - tableStyle: TableStyle.NORMAL, + tableStyle: TableStyle.DEFAULT, headers: ['', `${lookupDict['n_obs']} (${data['dependent_var']})`].map( (name) => ({ name, type: 'string' }), ), @@ -37,7 +37,7 @@ export default class LinearRegressionCVHandler extends BaseHandler { private getScores(data: any): TableResult | undefined { return { name: 'Scores', - tableStyle: TableStyle.NORMAL, + tableStyle: TableStyle.DEFAULT, headers: ['', 'Mean', 'Standard deviation'].map((name) => ({ name: name, type: 'string', @@ -56,14 +56,26 @@ export default class LinearRegressionCVHandler extends BaseHandler { return varible.label ?? id; } + canHandle(exp: Experiment, data: any): boolean { + return ( + exp.algorithm.name.toLowerCase() === ALGO_NAME && + data && + data[0] && + data[0].mean_sq_error && + data[0].r_squared + ); + } + handle(experiment: Experiment, data: any, domain: Domain): void { - if (experiment.algorithm.name.toLowerCase() !== ALGO_NAME) + if (!this.canHandle(experiment, data)) return super.handle(experiment, data, domain); + const extractedData = data[0]; + const varIds = [...experiment.variables, ...(experiment.coVariables ?? [])]; const variables = domain.variables.filter((v) => varIds.includes(v.id)); - let jsonData = JSON.stringify(data); + let jsonData = JSON.stringify(extractedData); variables.forEach((v) => { const regEx = new RegExp(v.id, 'gi'); 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 c2d6561e6268d278e3da3eed70a8264c67033ac0..04e9c0f20e4f16a0b2d571de4e991ece91101099 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 @@ -2,26 +2,28 @@ import { Domain } from 'src/engine/models/domain.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import LinearRegressionHandler from './linear-regression.handler'; -const data = { - dependent_var: 'lefthippocampus', - n_obs: 15431, - df_resid: 1540.0, - df_model: 2.0, - rse: 0.1270107560405171, - r_squared: 0.8772983534917347, - r_squared_adjusted: 0.8771390007040616, - f_stat: 5505.38441342865, - f_pvalue: 0.0, - indep_vars: ['Intercept', 'righthippocampus', 'leftamygdala'], - coefficients: [0.2185676251985193, 0.611894589820809, 1.0305204881766319], - std_err: [0.029052606790014847, 0.016978263425746872, 0.05180007458246667], - t_stats: [7.523167431352125, 36.03988078621131, 19.894189274496593], - pvalues: [ - 9.04278019740564e-14, 8.833386164556705e-207, 1.4580450464941301e-78, - ], - lower_ci: [0.16158077395909892, 0.5785916308422961, 0.9289143512210847], - upper_ci: [0.2755544764379397, 0.6451975487993219, 1.132126625132179], -}; +const data = [ + { + dependent_var: 'lefthippocampus', + n_obs: 15431, + df_resid: 1540.0, + df_model: 2.0, + rse: 0.1270107560405171, + r_squared: 0.8772983534917347, + r_squared_adjusted: 0.8771390007040616, + f_stat: 5505.38441342865, + f_pvalue: 0.0, + indep_vars: ['Intercept', 'righthippocampus', 'leftamygdala'], + coefficients: [0.2185676251985193, 0.611894589820809, 1.0305204881766319], + std_err: [0.029052606790014847, 0.016978263425746872, 0.05180007458246667], + t_stats: [7.523167431352125, 36.03988078621131, 19.894189274496593], + pvalues: [ + 9.04278019740564e-14, 8.833386164556705e-207, 1.4580450464941301e-78, + ], + lower_ci: [0.16158077395909892, 0.5785916308422961, 0.9289143512210847], + upper_ci: [0.2755544764379397, 0.6451975487993219, 1.132126625132179], + }, +]; const domain: Domain = { id: 'dummy-id', 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 b3b2801d9fa0d9fa88409786c7cd5dbfc2cef820..b58526d32447c3040571da65a6f6f138156efc00 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 @@ -34,8 +34,8 @@ export default class LinearRegressionHandler extends BaseHandler { const excepts = ['n_obs']; const tableModel: TableResult = { name: 'Model', - tableStyle: TableStyle.NORMAL, - headers: ['', 'name', 'value'].map((name) => ({ name, type: 'string' })), + tableStyle: TableStyle.DEFAULT, + headers: ['name', 'value'].map((name) => ({ name, type: 'string' })), data: [ 'dependent_var', 'n_obs', @@ -71,8 +71,8 @@ export default class LinearRegressionHandler extends BaseHandler { const tableCoef: TableResult = { name: 'Coefficients', - tableStyle: TableStyle.NORMAL, - headers: ['', ...keys].map((name) => ({ + tableStyle: TableStyle.DEFAULT, + headers: keys.map((name) => ({ name: lookupDict[name], type: 'string', })), @@ -95,18 +95,32 @@ export default class LinearRegressionHandler extends BaseHandler { return varible.label ?? id; } + canHandle(exp: Experiment, data: any): boolean { + return ( + exp.algorithm.name.toLowerCase() === ALGO_NAME && + data && + data[0] && + data[0].rse && + data[0].f_stat + ); + } + handle(experiment: Experiment, data: any, domain: Domain): void { - if (experiment.algorithm.name.toLowerCase() !== ALGO_NAME) + if (!this.canHandle(experiment, data)) return super.handle(experiment, data, domain); + const extractedData = data[0]; + const varIds = [...experiment.variables, ...(experiment.coVariables ?? [])]; const variables = domain.variables.filter((v) => varIds.includes(v.id)); - let jsonData = JSON.stringify(data); + let jsonData = JSON.stringify(extractedData); + 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(improvedData); 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 17abd4315ce007d48502d9924416720d50b2ba91..950298bc95046e11e80737d733b135125be63ddb 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 @@ -136,7 +136,7 @@ describe('Pearson result handler', () => { 0.47221482855748664, 0.5181316725208311, ], }, - 'p-values': { + p_values: { variables: [ 'leftputamen', 'leftttgtransversetemporalgyrus', @@ -228,7 +228,7 @@ describe('Pearson result handler', () => { 7.914506269450685e-103, 6.154408322606963e-127, ], }, - low_confidence_intervals: { + ci_lo: { variables: [ 'leftputamen', 'leftttgtransversetemporalgyrus', @@ -320,7 +320,7 @@ describe('Pearson result handler', () => { 0.4329922438384865, 0.48111874424540896, ], }, - high_confidence_intervals: { + ci_hi: { variables: [ 'leftputamen', 'leftttgtransversetemporalgyrus', @@ -418,7 +418,7 @@ describe('Pearson result handler', () => { const exp = createExperiment(); const names = [ 'correlations', - 'p-values', + 'p_values', 'low_confidence_intervals', 'high_confidence_intervals', ]; diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts index 40248088dcbb5aeff0a6b9b822d093743ed0607c..75d6610fd3434866d6d3d09c6526350c9269698e 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/pearson.handler.ts @@ -1,5 +1,6 @@ import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata' import { Expression } from 'jsonata'; +import { Domain } from 'src/engine/models/domain.model'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { HeatMapResult } from '../../../../models/result/heat-map-result.model'; import BaseHandler from '../base.handler'; @@ -7,16 +8,22 @@ import BaseHandler from '../base.handler'; export default class PearsonHandler extends BaseHandler { private static readonly transform: Expression = jsonata(` ( - $params := ['correlations', 'p-values', 'low_confidence_intervals', 'high_confidence_intervals']; + $params := ['correlations', 'p_values', 'ci_lo', 'ci_hi']; + $dictName := { + "correlations": "Correlations", + "p_values": "P values", + "ci_lo": 'Low confidence intervals', + "ci_hi": 'High confidence intervals' + }; $.$sift(function($v, $k) {$k in $params}).$each(function($v, $k) { { - 'name': $k, + 'name': $lookup($dictName, $k), 'xAxis': { 'categories': $v.variables }, 'yAxis': { - 'categories': $keys($v.$sift(function($val, $key) {$key ~> /^(?!variables$)/})) + 'categories': $reverse($v.variables) }, 'matrix': $v.$sift(function($val, $key) {$key ~> /^(?!variables$)/}).$each(function($val, $key) {$val})[] } @@ -28,34 +35,28 @@ export default class PearsonHandler extends BaseHandler { * @param {string} algorithm - The name of the algorithm to use. * @returns a boolean value. */ - canHandle(algorithm: string): boolean { - return algorithm.toLocaleLowerCase() === 'pearson'; + canHandle(algorithm: string, data: any): boolean { + return ( + algorithm.toLocaleLowerCase() === 'pearson' && + data && + data[0] && + data[0]['correlations'] && + data[0]['p_values'] + ); } - /** - * If the algorithm is Pearson, then transform the data into a HeatMapResult and push it into the - * results array - * @param {string} algorithm - The name of the algorithm. - * @param {unknown} data - The data that is passed to the algorithm. - * @param {AlgoResults} res - list of possible results - * @returns - */ - handle(exp: Experiment, data: unknown): void { - if (this.canHandle(exp.algorithm.name)) { - try { - const results = PearsonHandler.transform.evaluate( - data, - ) as HeatMapResult[]; - results - .filter((heatMap) => heatMap.matrix.length > 0 && heatMap.name) - .forEach((heatMap) => exp.results.push(heatMap)); - } catch (e) { - PearsonHandler.logger.warn( - 'An error occur when converting result from Pearson', - ); - PearsonHandler.logger.verbose(JSON.stringify(data)); - } - } + handle(exp: Experiment, data: any, domain?: Domain): void { + if (!this.canHandle(exp.algorithm.name, data)) + return super.handle(exp, data, domain); + + const extData = data[0]; + + const results = PearsonHandler.transform.evaluate( + extData, + ) as HeatMapResult[]; + results + .filter((heatMap) => heatMap.matrix.length > 0 && heatMap.name) + .forEach((heatMap) => exp.results.push(heatMap)); this.next?.handle(exp, data); } diff --git a/api/src/engine/connectors/exareme/handlers/algorithms/raw.handler.ts b/api/src/engine/connectors/exareme/handlers/algorithms/raw.handler.ts index 08cdac953300a1a06dee98568e37268a1999298c..c6d2f3268da9632421b9c8d692380d14e3cc0c65 100644 --- a/api/src/engine/connectors/exareme/handlers/algorithms/raw.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/algorithms/raw.handler.ts @@ -1,29 +1,37 @@ +import { Domain } from '../../../../models/domain.model'; import { MIME_TYPES } from '../../../../../common/interfaces/utilities.interface'; import { Experiment } from '../../../../models/experiment/experiment.model'; import { RawResult } from '../../../../models/result/raw-result.model'; import { ResultExperiment } from '../../interfaces/experiment/result-experiment.interface'; import BaseHandler from '../base.handler'; +const JSON_ALGO_LIST = ['cart', 'id3']; + export default class RawHandler extends BaseHandler { dataToRaw = (algo: string, result: ResultExperiment): RawResult => { let data = result; - if (algo === 'CART') { + if (algo === 'cart') { data = { ...data, type: MIME_TYPES.JSONBTREE }; } return { rawdata: data }; }; - handle(exp: Experiment, data: unknown): void { + handle(exp: Experiment, data: unknown, domain: Domain): void { const inputs = data as ResultExperiment[]; + const algoName = exp.algorithm.name.toLowerCase(); if (inputs && Array.isArray(inputs)) inputs .filter((input) => !!input.data && !!input.type) - .map((input) => this.dataToRaw(exp.algorithm.name, input)) + .filter( + (input) => + input.type !== MIME_TYPES.JSON || JSON_ALGO_LIST.includes(algoName), + ) + .map((input) => this.dataToRaw(algoName, input)) .forEach((input) => exp.results.push(input)); - this.next?.handle(exp, data); + super.handle(exp, data, domain); } } diff --git a/api/src/engine/connectors/exareme/handlers/base.handler.ts b/api/src/engine/connectors/exareme/handlers/base.handler.ts index 1aa3d2f6391922b10e1d9b29bf9c9a74fef0d575..6ab3074a48df252eb39b00fbdc0c1edd07106afb 100644 --- a/api/src/engine/connectors/exareme/handlers/base.handler.ts +++ b/api/src/engine/connectors/exareme/handlers/base.handler.ts @@ -13,7 +13,7 @@ export default abstract class BaseHandler implements ResultHandler { return h; } - handle(experiment: Experiment, data: unknown, domain: Domain): void { + handle(experiment: Experiment, data: unknown, domain?: Domain): void { this.next?.handle(experiment, data, domain); } } diff --git a/api/src/engine/connectors/exareme/interfaces/pathology.interface.ts b/api/src/engine/connectors/exareme/interfaces/pathology.interface.ts index e71e3a0251af2e03aa7e7995e6129609a9140cd0..7da8b77b983fd1e1489b23f7548ac2584604c658 100644 --- a/api/src/engine/connectors/exareme/interfaces/pathology.interface.ts +++ b/api/src/engine/connectors/exareme/interfaces/pathology.interface.ts @@ -4,6 +4,7 @@ import { VariableEntity } from './variable-entity.interface'; export interface Pathology { code: string; label: string; + version: string; datasets: VariableEntity[]; metadataHierarchy: Hierarchy; } diff --git a/api/src/engine/connectors/exareme/transformations.ts b/api/src/engine/connectors/exareme/transformations.ts index 60f6a78a7b9e9598eb5495e42493787f6c28ae96..70d2989c2ef4d4668287f56d92607b3ef3fb55be 100644 --- a/api/src/engine/connectors/exareme/transformations.ts +++ b/api/src/engine/connectors/exareme/transformations.ts @@ -22,7 +22,7 @@ export const transformToExperiment = jsonata(` "finishedAt": $convDate(finished), "shared": shared, "updateAt": $convDate(updated), - "domain": algorithm.parameters[name = "pathology"][0].value, + "domain": $split(algorithm.parameters[name = "pathology"][0].value, ':')[0], "datasets": $split(algorithm.parameters[name = "dataset"][0].value, ','), "variables": $split($rp(algorithm.parameters[name = "y"][0].value), ','), "coVariables": $toArray($split($rp(algorithm.parameters[name = "x"][0].value), ',')), diff --git a/api/src/engine/connectors/exareme/transformations/algorithms/index.ts b/api/src/engine/connectors/exareme/transformations/algorithms/index.ts index f177cbfdee60baf64000ad170a520fb097560b0f..b3626f3499aee15f47487f2704fd4f6226a03b1e 100644 --- a/api/src/engine/connectors/exareme/transformations/algorithms/index.ts +++ b/api/src/engine/connectors/exareme/transformations/algorithms/index.ts @@ -10,9 +10,9 @@ const transformToAlgorithms = jsonata(` $excludedParams:= ['centers', 'formula']; $includes:= ['ANOVA_ONEWAY','ANOVA','LINEAR_REGRESSION', 'LOGISTIC_REGRESSION','TTEST_INDEPENDENT','TTEST_PAIRED', - 'PEARSON_CORRELATION','ID3','KMEANS','NAIVE_BAYES', + 'PEARSON','ID3','KMEANS','NAIVE_BAYES', 'TTEST_ONESAMPLE','PCA','CALIBRATION_BELT','CART', - 'KAPLAN_MEIER','THREE_C']; + 'KAPLAN_MEIER','THREE_C', 'ONE_WAY_ANOVA', 'PEARSON', 'LINEAR_REGRESSION_CV']; $linkedVars:= ['positive_level', 'negative_level', 'outcome_neg', 'outcome_pos']; $linkedCoVars:= ['referencevalues', 'xlevels']; $truthy:= function($val) {( @@ -40,7 +40,7 @@ const transformToAlgorithms = jsonata(` "coVariable": parameters[(type='column' or type='formula') and name='x'] ~> $extract, "hasFormula": $boolean(parameters[(type='formula_description')]), "parameters": parameters[type='other' and $not(name in $excludedParams)].{ - "__typename": $lookup($dict, valueType), + "__typename": $lookup($dict, (valueType ? valueType : 'null')), "name": name, "label": label, "hint": $checkVal(desc), diff --git a/api/src/engine/engine.service.ts b/api/src/engine/engine.service.ts index 97ff0b4687613a9e8eb7863ab0e4814aa19d1ce6..70a7180033d724421c44589f206cca935130f9c4 100644 --- a/api/src/engine/engine.service.ts +++ b/api/src/engine/engine.service.ts @@ -221,18 +221,30 @@ export default class EngineService implements Connector { return this.connector.getFilterConfiguration(req); } - async logout?(req?: Request): Promise<void> { - const user = req?.user as User; - - if (user && user.id) - CACHE_KEYS.map((key) => `${key}-${user.id}`).forEach((key) => - this.cacheManager.del(key), - ); + async logout?(req: Request): Promise<void> { + await this.clearCache(req); if (!this.connector.logout) throw new NotImplementedException(); return this.connector.logout(req); } + /** + * It deletes all the cache keys for the current user + * @param {Request} req - Request - The request object. + * @returns A promise that resolves to an array of promises that resolve to undefined. + */ + async clearCache(req: Request): Promise<void> { + const user = req?.user as User; + + if (!user || !user.id) return; + + await Promise.all( + CACHE_KEYS.map((key) => `${key}-${user.id}`).map((key) => + this.cacheManager.del(key), + ), + ); + } + async login?(username: string, password: string): Promise<User> { if (!this.connector.login) throw new NotImplementedException(); return this.connector.login(username, password); diff --git a/api/src/engine/interceptors/errors.interceptor.ts b/api/src/engine/interceptors/errors.interceptor.ts index fa68a283ca34b0dee868c9c09e143d3c01a0109d..fee41f5e65e54c6f6f3f41d7da432dc7d207d41b 100644 --- a/api/src/engine/interceptors/errors.interceptor.ts +++ b/api/src/engine/interceptors/errors.interceptor.ts @@ -23,7 +23,7 @@ export class ErrorsInterceptor implements NestInterceptor { this.logger = new Logger(options.type); } - intercept(context: GqlExecutionContext, next: CallHandler): Observable<any> { + intercept(_context: GqlExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( catchError((e) => { if (!e.response || !e.response.data || !e.response.status) throw e; diff --git a/api/src/engine/models/domain.model.ts b/api/src/engine/models/domain.model.ts index 9fe826291a9f0932a30c6125e73a5f80d3c15981..35c27fe60e97dfc6743e0a589ce1da19c659502a 100644 --- a/api/src/engine/models/domain.model.ts +++ b/api/src/engine/models/domain.model.ts @@ -9,6 +9,9 @@ export class Domain extends BaseModel { @Field({ nullable: true }) description?: string; + @Field({ nullable: true }) + version?: string; + @Field(() => [Group]) groups: Group[]; diff --git a/api/src/schema.gql b/api/src/schema.gql index 417733367410fc6764fb214f008800cefafa4c4e..3720e78944a93b077e3e7458d088bf4c562dda53 100644 --- a/api/src/schema.gql +++ b/api/src/schema.gql @@ -86,6 +86,7 @@ type Domain { id: String! label: String description: String + version: String groups: [Group!]! variables: [Variable!]! datasets: [Dataset!]!