diff --git a/api/src/common/interfaces/utilities.interface.ts b/api/src/common/interfaces/utilities.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..ddf5dedfa9585bdffa46ef5a90ca643d49eacf7a --- /dev/null +++ b/api/src/common/interfaces/utilities.interface.ts @@ -0,0 +1 @@ +export type Dictionary<T> = { [key: string]: T }; diff --git a/api/src/engine/connectors/datashield/main.connector.ts b/api/src/engine/connectors/datashield/main.connector.ts index a3622654e507cc5747d93ecd287dd2fe81c28247..c81cc65fbef911bcb160acc7bdb516ec3aa80e1d 100644 --- a/api/src/engine/connectors/datashield/main.connector.ts +++ b/api/src/engine/connectors/datashield/main.connector.ts @@ -1,7 +1,12 @@ import { Observable } from 'rxjs'; import { IEngineService } from 'src/engine/engine.interfaces'; +import { Domain } from 'src/engine/models/domain.model'; export default class DataShieldService implements IEngineService { + getDomains(): Domain[] { + throw new Error('Method not implemented.'); + } + demo(): string { return 'datashield'; } diff --git a/api/src/engine/connectors/exareme/converters.ts b/api/src/engine/connectors/exareme/converters.ts new file mode 100644 index 0000000000000000000000000000000000000000..721c1bb5e091a7bba0c5eb8582c901ab4784c16f --- /dev/null +++ b/api/src/engine/connectors/exareme/converters.ts @@ -0,0 +1,35 @@ +import { Category } from 'src/engine/models/category.model'; +import { Group } from 'src/engine/models/group.model'; +import { Variable } from 'src/engine/models/variable.model'; +import { Hierarchy } from './interfaces/hierarchy.interface'; +import { VariableEntity } from './interfaces/variable-entity.interface'; +import { Entity } from './interfaces/entity.interface'; + +export const dataToGroup = (data: Hierarchy): Group => { + return { + id: data.code, + label: data.label, + groups: data.groups ? data.groups.map(dataToGroup) : [], + variables: [], + }; +}; + +export const dataToCategory = (data: Entity): Category => { + return { + id: data.code, + label: data.label, + }; +}; + +export const dataToVariable = (data: VariableEntity): Variable => { + return { + id: data.code, + label: data.label, + type: data.type, + description: data.description, + enumerations: data.enumerations + ? data.enumerations.map(dataToCategory) + : [], + groups: [], + }; +}; diff --git a/api/src/engine/connectors/exareme/interfaces/entity.interface.ts b/api/src/engine/connectors/exareme/interfaces/entity.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..38e3ab4125544a5b3eedf6f824533042f3c9d7d3 --- /dev/null +++ b/api/src/engine/connectors/exareme/interfaces/entity.interface.ts @@ -0,0 +1,4 @@ +export interface Entity { + code: string; + label?: string; +} diff --git a/api/src/engine/connectors/exareme/interfaces/hierarchy.interface.ts b/api/src/engine/connectors/exareme/interfaces/hierarchy.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..d5017f13c8d24134de164eb2a4da9064c69c4fe0 --- /dev/null +++ b/api/src/engine/connectors/exareme/interfaces/hierarchy.interface.ts @@ -0,0 +1,8 @@ +import { VariableEntity } from './variable-entity.interface'; + +export interface Hierarchy { + code: string; + label: string; + groups: Hierarchy[]; + variables: VariableEntity[]; +} diff --git a/api/src/engine/connectors/exareme/interfaces/pathology.interface.ts b/api/src/engine/connectors/exareme/interfaces/pathology.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..e71e3a0251af2e03aa7e7995e6129609a9140cd0 --- /dev/null +++ b/api/src/engine/connectors/exareme/interfaces/pathology.interface.ts @@ -0,0 +1,9 @@ +import { Hierarchy } from './hierarchy.interface'; +import { VariableEntity } from './variable-entity.interface'; + +export interface Pathology { + code: string; + label: string; + datasets: VariableEntity[]; + metadataHierarchy: Hierarchy; +} diff --git a/api/src/engine/connectors/exareme/interfaces/variable-entity.interface.ts b/api/src/engine/connectors/exareme/interfaces/variable-entity.interface.ts new file mode 100644 index 0000000000000000000000000000000000000000..06c880dcb97fb9e203a51067fe265c75492fef58 --- /dev/null +++ b/api/src/engine/connectors/exareme/interfaces/variable-entity.interface.ts @@ -0,0 +1,9 @@ +import { Entity } from './entity.interface'; + +export interface VariableEntity extends Entity { + type?: 'nominal' | 'ordinal' | 'real' | 'integer' | 'text' | 'date'; + description?: string; + enumerations?: Entity[]; + group?: Entity[]; + info?: string; +} diff --git a/api/src/engine/connectors/exareme/main.connector.ts b/api/src/engine/connectors/exareme/main.connector.ts index b85b494bec2fb8c1de2ac8bda441e9c71c3da241..ada843beff129d90701f93dd40b142f42b9496ae 100644 --- a/api/src/engine/connectors/exareme/main.connector.ts +++ b/api/src/engine/connectors/exareme/main.connector.ts @@ -1,7 +1,14 @@ import { HttpService } from '@nestjs/axios'; +import { HttpException, HttpStatus } from '@nestjs/common'; import { Request } from 'express'; -import { map, Observable } from 'rxjs'; +import { firstValueFrom, map, Observable } from 'rxjs'; import { IEngineOptions, IEngineService } from 'src/engine/engine.interfaces'; +import { Domain } from 'src/engine/models/domain.model'; +import { Group } from 'src/engine/models/group.model'; +import { Variable } from 'src/engine/models/variable.model'; +import { dataToCategory, dataToGroup, dataToVariable } from './converters'; +import { Hierarchy } from './interfaces/hierarchy.interface'; +import { Pathology } from './interfaces/pathology.interface'; export default class ExaremeService implements IEngineService { constructor( @@ -9,6 +16,37 @@ export default class ExaremeService implements IEngineService { private readonly httpService: HttpService, ) {} + async getDomains(ids: string[]): Promise<Domain[]> { + const path = this.options.baseurl + 'pathologies'; + + try { + const data = await firstValueFrom( + this.httpService.get<Pathology[]>(path), + ); + + return data.data + .filter((data) => !ids || ids.length == 0 || ids.includes(data.code)) + .map((data): Domain => { + const groups = this.flattenGroups(data.metadataHierarchy); + + return { + id: data.code, + label: data.label, + groups: groups, + datasets: data.datasets ? data.datasets.map(dataToCategory) : [], + variables: data.metadataHierarchy + ? this.flattenVariables(data.metadataHierarchy, groups) + : [], + }; + }); + } catch { + throw new HttpException( + `Connexion to the connector ${this.options.type} failed`, + HttpStatus.NOT_FOUND, + ); + } + } + demo(): string { return 'exareme'; } @@ -82,4 +120,31 @@ export default class ExaremeService implements IEngineService { .get<string>(path) .pipe(map((response) => response.data)); } + + private flattenGroups = (data: Hierarchy): Group[] => { + let groups: Group[] = [dataToGroup(data)]; + + if (data.groups) { + groups = groups.concat(data.groups.flatMap(this.flattenGroups)); + } + + return groups; + }; + + private flattenVariables = (data: Hierarchy, groups: Group[]): Variable[] => { + const group = groups.find((group) => group.id == data.code); + let variables = data.variables ? data.variables.map(dataToVariable) : []; + + variables.forEach((variable) => (variable.groups = group ? [group] : [])); + + if (data.groups) { + variables = variables.concat( + data.groups.flatMap((hierarchy) => + this.flattenVariables(hierarchy, groups), + ), + ); + } + + return variables; + }; } diff --git a/api/src/engine/engine.interfaces.ts b/api/src/engine/engine.interfaces.ts index 22f4a540d36de9b4d05ee98a529bac322a706d26..09b86b731d670a83b598936d834b8993f4a8b27f 100644 --- a/api/src/engine/engine.interfaces.ts +++ b/api/src/engine/engine.interfaces.ts @@ -1,5 +1,6 @@ import { Request } from 'express'; import { Observable } from 'rxjs'; +import { Domain } from './models/domain.model'; export interface IEngineOptions { type: string; @@ -9,6 +10,8 @@ export interface IEngineOptions { export interface IEngineService { demo(): string; + getDomains(ids: string[]): Domain[] | Promise<Domain[]>; + getAlgorithms(request: Request): Observable<string>; getExperiments(request: Request): Observable<string>; diff --git a/api/src/engine/engine.resolver.ts b/api/src/engine/engine.resolver.ts index 0e4687ca615da00101d0766b44fb7c3871e947b3..beeb589fc6de4f3294efffea16e4b55c1ac96f61 100644 --- a/api/src/engine/engine.resolver.ts +++ b/api/src/engine/engine.resolver.ts @@ -1,5 +1,5 @@ import { Inject } from '@nestjs/common'; -import { Query, Resolver } from '@nestjs/graphql'; +import { Args, Query, Resolver } from '@nestjs/graphql'; import { ENGINE_SERVICE } from './engine.constants'; import { IEngineService } from './engine.interfaces'; import { Domain } from './models/domain.model'; @@ -10,17 +10,11 @@ export class EngineResolver { @Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, ) {} - @Query(() => Domain) - async hello() { - const dummy: Domain = { - id: 'test', - label: 'test', - description: 'test', - groups: [], - variables: [], - datasets: [], - }; - - return dummy; + @Query(() => [Domain]) + async domains( + @Args('ids', { nullable: true, type: () => [String], defaultValue: [] }) + ids: string[], + ) { + return this.engineService.getDomains(ids); } } diff --git a/api/src/schema.gql b/api/src/schema.gql index 703c5eb6f1d38c98a2ebde2646150d24067e1848..130ebbf01bd3f0a75ab2f2d2a2e4cd6952394183 100644 --- a/api/src/schema.gql +++ b/api/src/schema.gql @@ -34,5 +34,5 @@ type Domain { } type Query { - hello: Domain! + domains(ids: [String!] = []): [Domain!]! }