Skip to content
Snippets Groups Projects
Commit fca79819 authored by Steve Reis's avatar Steve Reis
Browse files

Merge branch 'develop' into 'main'

Merge Request for the MIP 6.5 release

See merge request sibmip/gateway!24
parents b54b8207 995ab67e
No related branches found
No related tags found
No related merge requests found
Showing
with 763 additions and 46 deletions
// This file contains all transformation queries for JSONata
// see : https://docs.jsonata.org/
import * as jsonata from 'jsonata'; // old import style needed due to 'export = jsonata'
export const transformToAlgorithms = jsonata(`
(
$params := ["y", "pathology", "dataset", "filter"];
$toArray := function($x) { $type($x) = 'array' ? $x : [$x]};
*.{
'name': name,
'label': label,
'description': desc,
'parameters': $toArray(parameters[$not(name in $params)].{
'name': name,
'description': desc,
'label': label,
'type': valueType,
'defaultValue': defaultValue,
'isMultiple': $boolean(valueMultiple),
'isRequired': $boolean(valueNotBlank),
'min': valueMin,
'max': valueMax
})
}
)
`);
export const transformToExperiment = jsonata(`
(
$params := ["y", "pathology", "dataset", "filter"];
{
"name": name,
"uuid": uuid,
"author": createdBy,
"viewed": viewed,
"status": status,
"createdAt": created,
"finishedAt": finished,
"shared": shared,
"updateAt": updated,
"domains": algorithm.parameters[name = "pathology"].value,
"variables": $split(algorithm.parameters[name = "y"].value, ','),
"filter": algorithm.parameters[name = "filter"].value,
"datasets": $split(algorithm.parameters[name = "dataset"].value, ','),
"algorithm": {
"name": algorithm.name,
"parameters" :
algorithm.parameters[$not(name in $params)].({
"name": name,
"label": label,
"value": value
})
}
}
)
`);
const headerDescriptivie = `
$fnum := function($x) { $type($x) = 'number' ? $round($number($x),3) : $x };
$e := function($x, $r) {($x != null) ? $fnum($x) : ($r ? $r : '')};
$fn := function($o, $prefix) {
$type($o) = 'object' ?
$each($o, function($v, $k) {(
$type($v) = 'object' ? { $k: $v.count & ' (' & $v.percentage & '%)' } : {
$k: $v
}
)}) ~> $merge()
: {}
};`;
export const descriptiveModelToTables = jsonata(`
(
${headerDescriptivie}
$vars := $count($keys(data.model.*.data))-1;
$varNames := $keys(data.model.*.data);
$model := data.model;
[[0..$vars].(
$i := $;
$varName := $varNames[$i];
$ks := $keys($model.*.data.*[$i][$type($) = 'object']);
{
'name': $varName,
'headers': $append("", $keys($$.data.model)).{
'name': $,
'type': 'string'
},
'data': [
[$varName, $model.*.($e(num_total))],
['Datapoints', $model.*.($e(num_datapoints))],
['Nulls', $model.*.($e(num_nulls))],
($lookup($model.*.data, $varName).($fn($)) ~> $reduce(function($a, $b) {
$map($ks, function($k) {(
{
$k : [$e($lookup($a,$k), "No data"), $e($lookup($b,$k), "No data")]
}
)}) ~> $merge()
})).$each(function($v, $k) {$append($k,$v)})[]
]
}
)]
)`);
export const descriptiveSingleToTables = jsonata(`
(
${headerDescriptivie}
data.[
$.single.*@$p#$i.(
$ks := $keys($p.*.data[$type($) = 'object']);
{
'name': $keys(%)[$i],
'headers': $append("", $keys(*)).{
'name': $,
'type': 'string'
},
'data' : [
[$keys(%)[$i], $p.*.($e(num_total))],
['Datapoints', $p.*.($e(num_datapoints))],
['Nulls', $p.*.($e(num_nulls))],
($p.*.data.($fn($)) ~> $reduce(function($a, $b) {
$map($ks, function($k) {(
{
$k : [$e($lookup($a,$k), "No data"), $e($lookup($b,$k), "No data")]
}
)}) ~> $merge()
})).$each(function($v, $k) {$append($k,$v)})[]
]
})
]
)
`);
import { Observable } from 'rxjs';
import { IEngineService } from 'src/engine/engine.interfaces';
import { Domain } from 'src/engine/models/domain.model';
import { ExperimentCreateInput } from 'src/engine/models/experiment/input/experiment-create.input';
import {
Experiment,
PartialExperiment,
} from 'src/engine/models/experiment/experiment.model';
import { ListExperiments } from 'src/engine/models/experiment/list-experiments.model';
import { ExperimentEditInput } from 'src/engine/models/experiment/input/experiment-edit.input';
import { Algorithm } from 'src/engine/models/experiment/algorithm.model';
export default class LocalService implements IEngineService {
logout(): void {
throw new Error('Method not implemented.');
}
getAlgorithms(): Algorithm[] | Promise<Algorithm[]> {
throw new Error('Method not implemented.');
}
createExperiment(
data: ExperimentCreateInput,
isTransient: boolean,
): Experiment | Promise<Experiment> {
throw new Error('Method not implemented.');
}
listExperiments(
page: number,
name: string,
): ListExperiments | Promise<ListExperiments> {
throw new Error('Method not implemented.');
}
getExperiment(uuid: string): Experiment | Promise<Experiment> {
throw new Error('Method not implemented.');
}
removeExperiment(
uuid: string,
): PartialExperiment | Promise<PartialExperiment> {
throw new Error('Method not implemented.');
}
editExperient(
uuid: string,
expriment: ExperimentEditInput,
): Experiment | Promise<Experiment> {
throw new Error('Method not implemented.');
}
getDomains(): Domain[] {
return [
{
id: 'Dummy',
label: 'Dummy',
datasets: [{ id: 'DummyDataset', label: 'DummyDataset' }],
groups: [
{
id: 'DummyGroup',
variables: ['DummyVar'],
groups: [],
},
],
rootGroup: { id: 'DummyGroup' },
variables: [{ id: 'DummyVar', type: 'string' }],
},
];
}
getActiveUser(): string {
const dummyUser = {
username: 'anonymous',
subjectId: 'anonymousId',
fullname: 'anonymous',
email: 'anonymous@anonymous.com',
agreeNDA: true,
};
return JSON.stringify(dummyUser);
}
editActiveUser(): Observable<string> {
throw new Error('Method not implemented.');
}
getExperimentREST(): Observable<string> {
throw new Error('Method not implemented.');
}
deleteExperiment(): Observable<string> {
throw new Error('Method not implemented.');
}
editExperimentREST(): Observable<string> {
throw new Error('Method not implemented.');
}
startExperimentTransient(): Observable<string> {
throw new Error('Method not implemented.');
}
startExperiment(): Observable<string> {
throw new Error('Method not implemented.');
}
getExperiments(): string {
return '[]';
}
getAlgorithmsREST(): string {
return '[]';
}
}
export const ENGINE_MODULE_OPTIONS = "EngineModuleOption";
export const ENGINE_SERVICE = "EngineService"
\ No newline at end of file
export const ENGINE_MODULE_OPTIONS = 'EngineModuleOption';
export const ENGINE_SERVICE = 'EngineService';
import { HttpService } from '@nestjs/axios';
import { Controller, Get, Inject } from '@nestjs/common';
import {
Controller,
Delete,
Get,
Inject,
Param,
Patch,
Post,
UseInterceptors,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { ENGINE_SERVICE } from './engine.constants';
import { IEngineService } from './engine.interface';
import { IEngineService } from './engine.interfaces';
import { HeadersInterceptor } from './interceptors/headers.interceptor';
@UseInterceptors(HeadersInterceptor)
@Controller()
export class EngineController {
constructor(@Inject(ENGINE_SERVICE) private readonly engineService: IEngineService, private readonly httpService: HttpService) { }
constructor(
@Inject(ENGINE_SERVICE) private readonly engineService: IEngineService,
) {}
@Get("/test")
getTest(): string {
return this.engineService.demo();
@Get('/algorithms')
getAlgorithms(): Observable<string> | string {
return this.engineService.getAlgorithmsREST();
}
@Get('/experiments')
getExperiments(): Observable<string> | string {
return this.engineService.getExperiments();
}
@Get('/experiments/:uuid')
getExperiment(@Param('uuid') uuid: string): Observable<string> | string {
return this.engineService.getExperimentREST(uuid);
}
@Delete('/experiments/:uuid')
deleteExperiment(@Param('uuid') uuid: string): Observable<string> | string {
return this.engineService.deleteExperiment(uuid);
}
@Patch('/experiments/:uuid')
editExperiment(@Param('uuid') uuid: string): Observable<string> | string {
return this.engineService.editExperimentREST(uuid);
}
@Post('experiments/transient')
startExperimentTransient(): Observable<string> | string {
return this.engineService.startExperimentTransient();
}
@Post('experiments')
startExperiment(): Observable<string> | string {
return this.engineService.startExperiment();
}
@Get('activeUser')
getActiveUser(): Observable<string> | string {
return this.engineService.getActiveUser();
}
@Post('activeUser/agreeNDA')
agreeNDA(): Observable<string> | string {
return this.engineService.editActiveUser();
}
@Get('logout')
logout(): void {
this.engineService.logout();
}
}
import { AxiosResponse } from "axios";
import { Observable } from "rxjs";
export interface IEngineOptions {
type: string;
}
export interface IEngineService {
demo(): string;
}
\ No newline at end of file
import { Observable } from 'rxjs';
import { Domain } from './models/domain.model';
import { Algorithm } from './models/experiment/algorithm.model';
import {
Experiment,
PartialExperiment,
} from './models/experiment/experiment.model';
import { ExperimentCreateInput } from './models/experiment/input/experiment-create.input';
import { ExperimentEditInput } from './models/experiment/input/experiment-edit.input';
import { ListExperiments } from './models/experiment/list-experiments.model';
export interface IEngineOptions {
type: string;
baseurl: string;
}
export interface IEngineService {
//GraphQL
getDomains(ids: string[]): Domain[] | Promise<Domain[]>;
createExperiment(
data: ExperimentCreateInput,
isTransient: boolean,
): Promise<Experiment> | Experiment;
listExperiments(
page: number,
name: string,
): Promise<ListExperiments> | ListExperiments;
getExperiment(uuid: string): Promise<Experiment> | Experiment;
removeExperiment(
uuid: string,
): Promise<PartialExperiment> | PartialExperiment;
editExperient(
uuid: string,
expriment: ExperimentEditInput,
): Promise<Experiment> | Experiment;
getAlgorithms(): Promise<Algorithm[]> | Algorithm[];
// Standard REST API call
getAlgorithmsREST(): Observable<string> | string;
getExperiments(): Observable<string> | string;
getExperimentREST(uuid: string): Observable<string> | string;
deleteExperiment(uuid: string): Observable<string> | string;
editExperimentREST(uuid: string): Observable<string> | string;
startExperimentTransient(): Observable<string> | string;
startExperiment(): Observable<string> | string;
getActiveUser(): Observable<string> | string;
editActiveUser(): Observable<string> | string;
logout(): void;
}
import { HttpModule, HttpService } from '@nestjs/axios';
import { DynamicModule, Global, Module } from '@nestjs/common';
import { DynamicModule, Global, Logger, Module } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { GraphQLModule } from '@nestjs/graphql';
import { join } from 'path';
import { ENGINE_MODULE_OPTIONS, ENGINE_SERVICE } from './engine.constants';
import { EngineController } from './engine.controller';
import { IEngineOptions, IEngineService } from './engine.interface';
import { IEngineOptions, IEngineService } from './engine.interfaces';
import { EngineResolver } from './engine.resolver';
@Global()
@Module({})
export class EngineModule {
private static readonly logger = new Logger(EngineModule.name);
static async forRootAsync(options: IEngineOptions): Promise<DynamicModule> {
const optionsProvider = {
provide: ENGINE_MODULE_OPTIONS,
......@@ -18,10 +21,10 @@ export class EngineModule {
const engineProvider = {
provide: ENGINE_SERVICE,
useFactory: async (httpService: HttpService) => {
return await this.createEngineConnection(options, httpService)
useFactory: async (httpService: HttpService, req: Request) => {
return await this.createEngineConnection(options, httpService, req);
},
inject: [HttpService]
inject: [HttpService, REQUEST],
};
return {
......@@ -32,22 +35,29 @@ export class EngineModule {
autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
}),
],
providers: [
optionsProvider,
engineProvider,
EngineResolver
],
controllers: [
EngineController
],
providers: [optionsProvider, engineProvider, EngineResolver],
controllers: [EngineController],
exports: [optionsProvider, engineProvider],
}
};
}
private static async createEngineConnection(options: IEngineOptions, httpService: HttpService): Promise<IEngineService> {
let service = await import(`./connectors/${options.type}/main.connector`);
private static async createEngineConnection(
options: IEngineOptions,
httpService: HttpService,
req: Request,
): Promise<IEngineService> {
try {
const service = await import(
`./connectors/${options.type}/main.connector`
);
const engine = new service.default(options, httpService, req);
return new service.default(options, httpService);
return engine;
} catch {
this.logger.error(
`There is a problem with the connector '${options.type}'`,
);
process.exit(); // We can't continue without an engine, shutdown the process...
}
}
}
import { Inject } from '@nestjs/common';
import { Query, Resolver } from '@nestjs/graphql';
import { Inject, UseInterceptors } from '@nestjs/common';
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
import { ENGINE_SERVICE } from './engine.constants';
import { IEngineService } from './engine.interface';
import { IEngineService } from './engine.interfaces';
import { HeadersInterceptor } from './interceptors/headers.interceptor';
import { Domain } from './models/domain.model';
import { Algorithm } from './models/experiment/algorithm.model';
import {
Experiment,
PartialExperiment,
} from './models/experiment/experiment.model';
import { ExperimentCreateInput } from './models/experiment/input/experiment-create.input';
import { ExperimentEditInput } from './models/experiment/input/experiment-edit.input';
import { ListExperiments } from './models/experiment/list-experiments.model';
@UseInterceptors(HeadersInterceptor)
@Resolver()
export class EngineResolver {
constructor(@Inject(ENGINE_SERVICE) private readonly engineService: IEngineService) { }
constructor(
@Inject(ENGINE_SERVICE) private readonly engineService: IEngineService,
) {}
@Query(() => String)
async hello() {
return this.engineService.demo();
}
}
\ No newline at end of file
@Query(() => [Domain])
async domains(
@Args('ids', { nullable: true, type: () => [String], defaultValue: [] })
ids: string[],
) {
return this.engineService.getDomains(ids);
}
@Query(() => ListExperiments)
async experiments(
@Args('page', { nullable: true, defaultValue: 0 }) page: number,
@Args('name', { nullable: true, defaultValue: '' }) name: string,
) {
return this.engineService.listExperiments(page, name);
}
@Query(() => Experiment)
async expriment(@Args('uuid') uuid: string) {
return this.engineService.getExperiment(uuid);
}
@Query(() => [Algorithm])
async algorithms() {
return this.engineService.getAlgorithms();
}
@Mutation(() => Experiment)
async createExperiment(
@Args('data') experimentCreateInput: ExperimentCreateInput,
@Args('isTransient', { nullable: true, defaultValue: false })
isTransient: boolean,
) {
return this.engineService.createExperiment(
experimentCreateInput,
isTransient,
);
}
@Mutation(() => Experiment)
async editExperiment(
@Args('uuid') uuid: string,
@Args('data') experiment: ExperimentEditInput,
) {
return this.engineService.editExperient(uuid, experiment);
}
@Mutation(() => PartialExperiment)
async removeExperiment(
@Args('uuid') uuid: string,
): Promise<PartialExperiment> {
return this.engineService.removeExperiment(uuid);
}
}
import { HttpService } from '@nestjs/axios';
import { Injectable, NestInterceptor, CallHandler } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { IncomingMessage } from 'http';
import { Observable, tap } from 'rxjs';
@Injectable()
export class HeadersInterceptor implements NestInterceptor {
constructor(private httpService: HttpService) {}
intercept(context: GqlExecutionContext, next: CallHandler): Observable<any> {
// cleaner : add only the auth header (should find the name)
const keys = ['cookie', 'x-xsrf-token'];
let headers = {};
switch (context.getType()) {
case 'http': {
const ctx = context.switchToHttp();
const request = ctx.getRequest<Request>();
headers = request.headers;
break;
}
case 'graphql': {
const ctx = GqlExecutionContext.create(context);
const req: IncomingMessage = ctx.getContext().req;
headers = req.headers;
break;
}
}
Object.keys(headers)
.filter((key) => keys.includes(key))
.map((key) => key.toLowerCase())
.forEach((key) => {
this.httpService.axiosRef.defaults.headers.common[key] = headers[key];
});
return next.handle().pipe(
tap(() => {
this.httpService.axiosRef.defaults.headers.common = {}; //cleaning request
}),
);
}
}
import { ObjectType } from '@nestjs/graphql';
import { Entity } from './entity.model';
@ObjectType()
export class Category extends Entity {}
import { Field, ObjectType } from '@nestjs/graphql';
import { Category } from './category.model';
import { Entity } from './entity.model';
import { Group } from './group.model';
import { Variable } from './variable.model';
@ObjectType()
export class Domain extends Entity {
@Field({ nullable: true })
description?: string;
@Field(() => [Group])
groups: Group[];
@Field(() => [Variable])
variables: Variable[];
@Field(() => [Category])
datasets: Category[];
@Field(() => Group)
rootGroup: Group;
}
import { Field, InputType, ObjectType } from '@nestjs/graphql';
@InputType()
@ObjectType()
export class Entity {
@Field()
id: string;
@Field({ nullable: true })
label?: string;
}
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class AlgorithmParameter {
@Field()
name: string;
@Field(() => [String], { nullable: true })
value?: string[];
@Field({ nullable: true })
label?: string;
@Field({ nullable: true })
description?: string;
@Field({ nullable: true })
defaultValue?: string;
@Field({ defaultValue: false, nullable: true })
isMultiple?: boolean;
@Field({ defaultValue: false, nullable: true })
isRequired?: boolean;
@Field({ nullable: true })
min?: string;
@Field({ nullable: true })
max?: string;
@Field({ nullable: true })
type?: string;
}
import { Field, ObjectType } from '@nestjs/graphql';
import { AlgorithmParameter } from './algorithm-parameter.model';
@ObjectType()
export class Algorithm {
@Field()
name: string;
@Field(() => [AlgorithmParameter], { nullable: true, defaultValue: [] })
parameters?: AlgorithmParameter[];
@Field({ nullable: true })
label?: string;
@Field({ nullable: true })
type?: string;
@Field({ nullable: true })
description?: string;
}
import { Field, ObjectType, PartialType } from '@nestjs/graphql';
import { ResultUnion } from '../result/common/result-union.model';
import { Algorithm } from './algorithm.model';
@ObjectType()
export class Experiment {
@Field({ nullable: true })
uuid?: string;
@Field({ nullable: true, defaultValue: '' })
author?: string;
@Field({ nullable: true })
createdAt?: number;
@Field({ nullable: true })
updateAt?: number;
@Field({ nullable: true })
finishedAt?: number;
@Field({ nullable: true, defaultValue: false })
viewed?: boolean;
@Field({ nullable: true })
status?: string;
@Field({ defaultValue: false })
shared?: boolean;
@Field(() => [ResultUnion], { nullable: true, defaultValue: [] })
results?: Array<typeof ResultUnion>;
@Field(() => [String])
datasets: string[];
@Field(() => String, { nullable: true })
filter?: string;
@Field()
domain: string;
@Field(() => [String])
variables: string[];
@Field()
algorithm: Algorithm;
@Field()
name: string;
}
@ObjectType()
export class PartialExperiment extends PartialType(Experiment) {}
import { Field, InputType } from '@nestjs/graphql';
@InputType()
export class AlgorithmParamInput {
@Field()
name: string;
@Field(() => [String])
value: string[];
}
import { Field, InputType } from '@nestjs/graphql';
import { AlgorithmParamInput } from './algorithm-parameter.input';
@InputType()
export class AlgorithmInput {
@Field()
name: string;
@Field(() => [AlgorithmParamInput], { nullable: true, defaultValue: [] })
parameters: AlgorithmParamInput[];
@Field()
type: string;
}
import { Field, InputType } from '@nestjs/graphql';
import { AlgorithmInput } from './algorithm.input';
@InputType()
export class FormulaTransformation {
@Field()
name: string;
@Field()
operation: string;
}
@InputType()
export class ExperimentCreateInput {
@Field(() => [String])
datasets: string[];
@Field(() => String, { nullable: true })
filter: string;
@Field()
domain: string;
@Field(() => [String])
variables: string[];
@Field()
algorithm: AlgorithmInput;
@Field()
name: string;
@Field(() => [FormulaTransformation], { nullable: true })
transformations: FormulaTransformation[];
@Field(() => [[String]], { nullable: true })
interactions: string[][];
}
import { Field, InputType } from '@nestjs/graphql';
@InputType()
export class ExperimentEditInput {
@Field({ nullable: true })
name?: string;
@Field({ nullable: true })
viewed?: boolean;
}
import { Field, ObjectType } from '@nestjs/graphql';
import { Experiment } from './experiment.model';
@ObjectType()
export class ListExperiments {
@Field({ nullable: true, defaultValue: 0 })
currentPage?: number;
@Field({ nullable: true })
totalPages?: number;
@Field({ nullable: true })
totalExperiments?: number;
@Field(() => [Experiment])
experiments: Experiment[];
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment