From 5f0c8a5b30ffab97bad3f2d3d452d318540c014e Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 22 Feb 2024 13:13:30 +0100 Subject: [PATCH 01/50] feat(json validation): add validation for options --- package-lock.json | 6 + package.json | 1 + packages/demo/public/options.json | 2 - packages/lib/src/components/Options.wc.svelte | 13 ++ .../lib/src/interfaces/options.schema.json | 153 ++++++++++++++++++ 5 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 packages/lib/src/interfaces/options.schema.json diff --git a/package-lock.json b/package-lock.json index 8347968f..240184e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.3-0", "license": "MIT", "dependencies": { + "@exodus/schemasafe": "^1.3.0", "chart.js": "^4.4.0", "svelte-dnd-action": "^0.9.26", "uuid": "^9.0.0" @@ -492,6 +493,11 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@exodus/schemasafe": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz", + "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", diff --git a/package.json b/package.json index 9cc75ef1..ce963b9c 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "vitest": "^0.34.2" }, "dependencies": { + "@exodus/schemasafe": "^1.3.0", "chart.js": "^4.4.0", "svelte-dnd-action": "^0.9.26", "uuid": "^9.0.0" diff --git a/packages/demo/public/options.json b/packages/demo/public/options.json index 7dc8d0fa..7c7fe913 100644 --- a/packages/demo/public/options.json +++ b/packages/demo/public/options.json @@ -145,8 +145,6 @@ { "stratifierCode": "Histlogoies", "stratumCode": "1" - }, - { } ] } diff --git a/packages/lib/src/components/Options.wc.svelte b/packages/lib/src/components/Options.wc.svelte index a3db079a..0cbb5b7c 100644 --- a/packages/lib/src/components/Options.wc.svelte +++ b/packages/lib/src/components/Options.wc.svelte @@ -16,10 +16,23 @@ import { lensOptions } from "../stores/options"; import { catalogue } from "../stores/catalogue"; import type { Criteria } from "../types/treeData"; + import optionsSchema from "../interfaces/options.schema.json"; + import { parser } from "@exodus/schemasafe"; export let options: object = {}; export let catalogueData: Criteria[] = []; + /** + * Validate the options against the schema + */ + const parse = parser(optionsSchema, { includeErrors: true }); + $: { + const validJSON = parse(JSON.stringify(options)); + if (options !== {} && options !== "" && validJSON.errors) { + console.error("Lens-Options: ", validJSON.errors); + } + } + $: $lensOptions = options; $: $catalogue = catalogueData; diff --git a/packages/lib/src/interfaces/options.schema.json b/packages/lib/src/interfaces/options.schema.json new file mode 100644 index 00000000..d07b0476 --- /dev/null +++ b/packages/lib/src/interfaces/options.schema.json @@ -0,0 +1,153 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/product.schema.json", + "title": "Product", + "description": "A product in the catalog", + "type": "object", + "properties": { + "chartOptions": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "object", + "properties": { + "legendMapping": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "string", + "pattern": "^.+$" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + }, + "hintText": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$" + } + }, + "aggregations": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$" + } + }, + "tooltips": { + "type": "object", + "patternProperties": { + "^.+$": { + "type": "string", + "pattern": "^.+$" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + }, + "tableOptions": { + "type": "object", + "properties": { + "headerData": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "pattern": "^.+$" + }, + "dataKey": { + "type": "string", + "pattern": "^.+$" + }, + "aggregatedDataKeys": { + "type": "array", + "items": { + "type": "object", + "properties": { + "groupCode": { + "type": "string", + "pattern": "^.+$" + }, + "stratifierCode": { + "type": "string", + "pattern": "^.+$" + }, + "stratumCode": { + "type": "string", + "pattern": "^.+$" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + } + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [ + "title" + ] + } + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": ["headerData"] + }, + "resultSummaryOptions": { + "type": "object", + "properties": { + "title": { + "type": "string", + "pattern": "^.+$" + }, + "infoButtonText": { + "type": "string", + "pattern": "^.+$" + }, + "dataTypes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string", + "pattern": "^.+$" + }, + "dataKey": { + "type": "string", + "pattern": "^.+$" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + } + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] +} \ No newline at end of file From dce505d5f72a8ca73013fcdde0e2e2dac1af1dd9 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Mon, 26 Feb 2024 15:41:08 +0100 Subject: [PATCH 02/50] feat(json validation): add icon urls to options --- .../lib/src/interfaces/options.schema.json | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/interfaces/options.schema.json b/packages/lib/src/interfaces/options.schema.json index d07b0476..4f492bf2 100644 --- a/packages/lib/src/interfaces/options.schema.json +++ b/packages/lib/src/interfaces/options.schema.json @@ -5,6 +5,29 @@ "description": "A product in the catalog", "type": "object", "properties": { + "iconOptions": { + "type": "object", + "properties": { + "infoUrl": { + "type": "string", + "pattern": "^.+$", + "description": "The icon to use for the info button" + }, + "addUrl": { + "type": "string", + "pattern": "^.+$", + "description": "The icon to use for the add button in the catalogue" + }, + "toggleUrl": { + "type": "string", + "pattern": "^.+$", + "description": "The icon to use for the toggle button in the catalogue" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + }, "chartOptions": { "type": "object", "patternProperties": { @@ -27,11 +50,13 @@ "type": "array", "items": { "type": "string", - "pattern": "^.+$" + "pattern": "^.+$", + "description": "The hint text to display as overlay of the info button" } }, "aggregations": { "type": "array", + "description": "add strings of other data keys to include in the chart", "items": { "type": "string", "pattern": "^.+$" @@ -42,7 +67,8 @@ "patternProperties": { "^.+$": { "type": "string", - "pattern": "^.+$" + "pattern": "^.+$", + "description": "The tooltip to display while hovering over the chart data" } }, "additionalProperties": false, @@ -69,14 +95,17 @@ "properties": { "title": { "type": "string", + "description": "the title of the column", "pattern": "^.+$" }, "dataKey": { "type": "string", + "description": "a single key to display in the table", "pattern": "^.+$" }, "aggregatedDataKeys": { "type": "array", + "description": "an array of keys to aggregate and display in the table as single value", "items": { "type": "object", "properties": { @@ -134,6 +163,30 @@ "dataKey": { "type": "string", "pattern": "^.+$" + }, + "aggregatedDataKeys": { + "type": "array", + "description": "an array of keys to aggregate and display in the result summary as single value", + "items": { + "type": "object", + "properties": { + "groupCode": { + "type": "string", + "pattern": "^.+$" + }, + "stratifierCode": { + "type": "string", + "pattern": "^.+$" + }, + "stratumCode": { + "type": "string", + "pattern": "^.+$" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + } } }, "additionalProperties": false, From e2d6942aecd58e8c3975e23522f4f70623600f12 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Tue, 27 Feb 2024 13:55:03 +0100 Subject: [PATCH 03/50] feat(json validation): add catalogue schema --- packages/lib/src/components/Options.wc.svelte | 30 +++- .../lib/src/interfaces/catalogue.schema.json | 130 ++++++++++++++++++ .../lib/src/interfaces/options.schema.json | 5 +- 3 files changed, 156 insertions(+), 9 deletions(-) create mode 100644 packages/lib/src/interfaces/catalogue.schema.json diff --git a/packages/lib/src/components/Options.wc.svelte b/packages/lib/src/components/Options.wc.svelte index 0cbb5b7c..e413ba12 100644 --- a/packages/lib/src/components/Options.wc.svelte +++ b/packages/lib/src/components/Options.wc.svelte @@ -17,22 +17,40 @@ import { catalogue } from "../stores/catalogue"; import type { Criteria } from "../types/treeData"; import optionsSchema from "../interfaces/options.schema.json"; + import catalogueSchema from "../interfaces/catalogue.schema.json"; import { parser } from "@exodus/schemasafe"; + import type { LensOptions } from "../types/options"; - export let options: object = {}; + export let options: LensOptions = {}; export let catalogueData: Criteria[] = []; /** - * Validate the options against the schema + * Validate the options against the schema before passing them to the store */ - const parse = parser(optionsSchema, { includeErrors: true }); + $: { + const parse = parser(optionsSchema, { + includeErrors: true, + allErrors: true, + }); const validJSON = parse(JSON.stringify(options)); - if (options !== {} && options !== "" && validJSON.errors) { + if (validJSON.valid === true) { + $lensOptions = options; + } else if (typeof options === "object") { console.error("Lens-Options: ", validJSON.errors); } } - $: $lensOptions = options; - $: $catalogue = catalogueData; + $: { + const parse = parser(catalogueSchema, { + includeErrors: true, + allErrors: true, + }); + const validJSON = parse(JSON.stringify(catalogueData)); + if (validJSON.valid === true) { + $catalogue = catalogueData; + } else if (typeof catalogueData === "object") { + console.error("Lens-Options: ", validJSON.errors); + } + } diff --git a/packages/lib/src/interfaces/catalogue.schema.json b/packages/lib/src/interfaces/catalogue.schema.json new file mode 100644 index 00000000..e6e12456 --- /dev/null +++ b/packages/lib/src/interfaces/catalogue.schema.json @@ -0,0 +1,130 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Search Parameter Catalogue", + "description": "A catalogue of search parameters", + "type": "array", + "items": { + "$ref": "#/$defs/categoryItem" + }, + "$defs": { + "childCategories": { + "type": "array", + "items": { + "$ref": "#/$defs/categoryItem" + } + }, + "categoryItem": { + "type": "object", + "properties": { + "key": { + "type": "string", + "pattern": "^.+$" + }, + "name": { + "type": "string", + "pattern": "^.+$" + }, + "subCategoryName": { + "type": "string", + "pattern": "^.+$" + }, + "infoButtonText": { + "type": "array", + "description": "The text to display in the info button", + "items": { + "type": "string", + "pattern": "^.*$" + } + }, + "system": { + "type": "string", + "pattern": "^.*$" + }, + "fieldType": { + "enum": [ + "single-select", + "number", + "autocomplete" + ] + }, + "type": { + "enum": [ + "EQUALS", + "BETWEEN" + ] + }, + "childCategories": { + "$ref": "#/$defs/childCategories" + }, + "criteria": { + "$ref": "#/$defs/criteria" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [ + "key", + "name" + ] + }, + "criteria": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string", + "pattern": "^.+$" + }, + "name": { + "type": "string", + "pattern": "^.+$" + }, + "description": { + "type": "string", + "pattern": "^.*$" + }, + "infoButtonText": { + "type": "array", + "description": "The text to display in the info button", + "items": { + "type": "string", + "pattern": "^.+$" + } + }, + "aggregatedValue": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "pattern": "^.+$" + }, + "name": { + "type": "string", + "pattern": "^.+$" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [ + "value", + "name" + ] + } + } + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [ + "key", + "name" + ] + } + } + } +} \ No newline at end of file diff --git a/packages/lib/src/interfaces/options.schema.json b/packages/lib/src/interfaces/options.schema.json index 4f492bf2..13907a75 100644 --- a/packages/lib/src/interfaces/options.schema.json +++ b/packages/lib/src/interfaces/options.schema.json @@ -1,8 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://example.com/product.schema.json", - "title": "Product", - "description": "A product in the catalog", + "title": "Lens Options", + "description": "The options for the lens", "type": "object", "properties": { "iconOptions": { From 3f79c54d3909ddab1643fbf3c3a89e4c71f5f148 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 29 Feb 2024 09:37:46 +0100 Subject: [PATCH 04/50] feat(WIP backends): add multiple backends --- packages/demo/public/options.json | 127 ++++++++++++++++- packages/demo/src/AppCCP.svelte | 74 ++-------- packages/demo/src/backends/blaze.ts | 109 +++++++++++++++ packages/demo/src/backends/spot.ts | 128 ++++++++++++++++++ packages/lib/src/classes/spot.ts | 8 +- .../lib/src/components/DataPasser.wc.svelte | 8 ++ packages/lib/src/components/Options.wc.svelte | 7 +- .../buttons/SearchButtonComponenet.wc.svelte | 71 +++++----- packages/lib/src/types/backend.ts | 16 ++- 9 files changed, 440 insertions(+), 108 deletions(-) create mode 100644 packages/demo/src/backends/blaze.ts create mode 100644 packages/demo/src/backends/spot.ts diff --git a/packages/demo/public/options.json b/packages/demo/public/options.json index 7dc8d0fa..e9b379ef 100644 --- a/packages/demo/public/options.json +++ b/packages/demo/public/options.json @@ -102,7 +102,7 @@ "rna": "RNA", "derivative-other": "Derivat, Andere" }, - "legendMapping":{ + "legendMapping": { "tissue-ffpe": "Gewebe FFPE", "tissue-frozen": "Gewebe schockgefroren", "tissue-other": "Gewebe, Andere Konservierungsart", @@ -146,8 +146,7 @@ "stratifierCode": "Histlogoies", "stratumCode": "1" }, - { - } + {} ] } ] @@ -165,5 +164,127 @@ "dataKey": "patients" } ] + }, + "backends": { + "spots": [ + { + "url": "http://localhost:8080", + "sites": [ + "berlin", + "berlin-test", + "bonn", + "dresden", + "essen", + "frankfurt", + "freiburg", + "hannover", + "mainz", + "muenchen-lmu", + "muenchen-tum", + "ulm", + "wuerzburg", + "mannheim", + "dktk-test", + "hamburg" + ], + "uiSiteMap": [ + [ + "berlin", + "Berlin" + ], + [ + "berlin-test", + "Berlin Test" + ], + [ + "bonn", + "Bonn" + ], + [ + "dresden", + "Dresden" + ], + [ + "essen", + "Essen" + ], + [ + "frankfurt", + "Frankfurt" + ], + [ + "freiburg", + "Freiburg" + ], + [ + "hannover", + "Hannover" + ], + [ + "mainz", + "Mainz" + ], + [ + "muenchen-lmu", + "München(LMU)" + ], + [ + "muenchen-tum", + "München(TUM)" + ], + [ + "ulm", + "Ulm" + ], + [ + "wuerzburg", + "Würzburg" + ], + [ + "mannheim", + "Mannheim" + ], + [ + "dktk-test", + "DKTK-Test" + ], + [ + "hamburg", + "Hamburg" + ] + ], + "catalogueKeyToResponseKeyMap": [ + [ + "gender", + "Gender" + ], + [ + "age_at_diagnosis", + "Age" + ], + [ + "diagnosis", + "diagnosis" + ], + [ + "medicationStatements", + "MedicationType" + ], + [ + "sample_kind", + "sample_kind" + ], + [ + "therapy_of_tumor", + "ProcedureType" + ], + [ + "75186-7", + "75186-7" + ] + ] + } + ], + "blazes": [] } } \ No newline at end of file diff --git a/packages/demo/src/AppCCP.svelte b/packages/demo/src/AppCCP.svelte index 616df348..41e122d8 100644 --- a/packages/demo/src/AppCCP.svelte +++ b/packages/demo/src/AppCCP.svelte @@ -46,41 +46,11 @@ let catalogueopen = false; - const catalogueKeyToResponseKeyMap = [ - ["gender", "Gender"], - ["age_at_diagnosis", "Age"], - ["diagnosis", "diagnosis"], - ["medicationStatements", "MedicationType"], - ["sample_kind", "sample_kind"], - ["therapy_of_tumor", "ProcedureType"], - ["75186-7", "75186-7"], - // ["encounter", "Encounter"], - ]; - - // VITE_TARGET_ENVIRONMENT should be set by the ci pipeline - const backendUrl = - import.meta.env.VITE_TARGET_ENVIRONMENT === "production" - ? "https://backend.data.dktk.dkfz.de/prod/" - : "https://backend.demo.lens.samply.de/prod/"; - - const uiSiteMap: string[][] = [ - ["berlin", "Berlin"], - ["berlin-test", "Berlin"], - ["bonn", "Bonn"], - ["dresden", "Dresden"], - ["essen", "Essen"], - ["frankfurt", "Frankfurt"], - ["freiburg", "Freiburg"], - ["hannover", "Hannover"], - ["mainz", "Mainz"], - ["muenchen-lmu", "München(LMU)"], - ["muenchen-tum", "München(TUM)"], - ["ulm", "Ulm"], - ["wuerzburg", "Würzburg"], - ["mannheim", "Mannheim"], - ["dktk-test", "DKTK-Test"], - ["hamburg", "Hamburg"], - ]; + // // VITE_TARGET_ENVIRONMENT should be set by the ci pipeline + // const backendUrl = + // import.meta.env.VITE_TARGET_ENVIRONMENT === "production" + // ? "https://backend.data.dktk.dkfz.de/prod/" + // : "https://backend.demo.lens.samply.de/prod/"; const genderHeaders: Map = new Map() .set("male", "männlich") @@ -88,27 +58,6 @@ .set("other", "Divers, Intersexuell") .set("unknown", "unbekannt"); - const backendConfig = { - url: import.meta.env.PROD ? backendUrl : "http://localhost:8080", - backends: [ - "mannheim", - "freiburg", - "muenchen-tum", - "hamburg", - "frankfurt", - "berlin-test", - "dresden", - "mainz", - "muenchen-lmu", - "essen", - "ulm", - "wuerzburg", - "hannover", - ], - uiSiteMap: uiSiteMap, - catalogueKeyToResponseKeyMap: catalogueKeyToResponseKeyMap, - }; - const barChartBackgroundColors: string[] = ["#4dc9f6", "#3da4c7"]; const vitalStateHeaders: Map = new Map() @@ -145,12 +94,7 @@ noQueryMessage="Leere Suchanfrage: Sucht nach allen Ergebnissen." showQuery={true} /> - +
@@ -288,4 +232,8 @@
- + diff --git a/packages/demo/src/backends/blaze.ts b/packages/demo/src/backends/blaze.ts new file mode 100644 index 00000000..a475acd7 --- /dev/null +++ b/packages/demo/src/backends/blaze.ts @@ -0,0 +1,109 @@ +import { buildLibrary, buildMeasure } from "../helpers/cql-measure"; +import { responseStore } from "../stores/response"; +import type { Site } from "../types/response"; +import { measureStore } from "../stores/measures"; + +let measureDefinitions; + +measureStore.subscribe((store) => { + measureDefinitions = store.map((measure) => measure.measure); +}); + +export class Blaze { + constructor( + private url: URL, + private name: string, + private auth: string = "", + ) {} + + /** + * sends the query to beam and updates the store with the results + * @param cql the query as cql string + * @param controller the abort controller to cancel the request + */ + async send(cql: string, controller?: AbortController): Promise { + try { + responseStore.update((store) => { + store.set(this.name, { status: "claimed", data: null }); + return store; + }); + const libraryResponse = await fetch( + new URL(`${this.url}/Library`), + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(buildLibrary(cql)), + signal: controller?.signal, + }, + ); + if (!libraryResponse.ok) { + this.handleError( + `Couldn't create Library in Blaze`, + libraryResponse, + ); + } + const library = await libraryResponse.json(); + const measureResponse = await fetch( + new URL(`${this.url}/Measure`), + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify( + buildMeasure(library.url, measureDefinitions), + ), + signal: controller.signal, + }, + ); + if (!measureResponse.ok) { + this.handleError( + `Couldn't create Measure in Blaze`, + measureResponse, + ); + } + const measure = await measureResponse.json(); + const dataResponse = await fetch( + new URL( + `${this.url}/Measure/$evaluate-measure?measure=${measure.url}&periodStart=2000&periodEnd=2030`, + ), + { + signal: controller.signal, + }, + ); + if (!dataResponse.ok) { + this.handleError( + `Couldn't evaluate Measure in Blaze`, + dataResponse, + ); + } + const blazeResponse: Site = await dataResponse.json(); + responseStore.update((store) => { + store.set(this.name, { + status: "succeeded", + data: blazeResponse, + }); + return store; + }); + } catch (err) { + if (err.name === "AbortError") { + console.log(`Aborting former blaze request.`); + } else { + console.error(err); + } + } + } + + async handleError(message: string, response: Response): Promise { + const errorMessage = await response.text(); + console.debug( + `${message}. Received error ${response.status} with message ${errorMessage}`, + ); + responseStore.update((store) => { + store.set(this.name, { status: "permfailed", data: null }); + return store; + }); + } +} diff --git a/packages/demo/src/backends/spot.ts b/packages/demo/src/backends/spot.ts new file mode 100644 index 00000000..f4725106 --- /dev/null +++ b/packages/demo/src/backends/spot.ts @@ -0,0 +1,128 @@ +/** + * TODO: document this class + */ + +import { responseStore } from "../stores/response"; +import type { ResponseStore } from "../types/backend"; + +import type { Site, SiteData, Status } from "../types/response"; + +type BeamResult = { + body: string; + from: string; + metadata: string; + status: Status; + task: string; + to: string[]; +}; + +export class Spot { + private storeCache!: ResponseStore; + private currentTask!: string; + + constructor( + private url: URL, + private sites: Array, + ) { + responseStore.subscribe( + (store: ResponseStore) => (this.storeCache = store), + ); + } + + /** + * sends the query to beam and updates the store with the results + * @param query the query as base64 encoded string + * @param controller the abort controller to cancel the request + */ + async send(query: string, controller?: AbortController): Promise { + try { + const beamTaskResponse = await fetch( + `${this.url}tasks?sites=${this.sites.toString()}`, + { + method: "POST", + credentials: import.meta.env.PROD ? "include" : "omit", + body: query, + signal: controller?.signal, + }, + ); + if (!beamTaskResponse.ok) { + const error = await beamTaskResponse.text(); + console.debug( + `Received ${beamTaskResponse.status} with message ${error}`, + ); + throw new Error(`Unable to create new beam task.`); + } + this.currentTask = (await beamTaskResponse.json()).id; + + let responseCount: number = 0; + + do { + const beamResponses: Response = await fetch( + `${this.url}tasks/${this.currentTask}?wait_count=${responseCount + 1}`, + { + credentials: import.meta.env.PROD ? "include" : "omit", + signal: controller?.signal, + }, + ); + + if (!beamResponses.ok) { + const error: string = await beamResponses.text(); + console.debug( + `Received ${beamResponses.status} with message ${error}`, + ); + throw new Error( + `Error then retrieving responses from Beam. Abborting requests ...`, + ); + } + + const beamResponseData: Array = + await beamResponses.json(); + + const changes = new Map(); + beamResponseData.forEach((response: BeamResult) => { + if (response.task !== this.currentTask) return; + const site: string = response.from.split(".")[1]; + const status: Status = response.status; + const body: SiteData = + status === "succeeded" + ? JSON.parse(atob(response.body)) + : null; + + // if the site is already in the store and the status is claimed, don't update the store + if (this.storeCache.get(site)?.status === status) return; + + changes.set(site, { status: status, data: body }); + }); + if (changes.size > 0) { + responseStore.update( + (store: ResponseStore): ResponseStore => { + changes.forEach((value, key) => { + store.set(key, value); + }); + return store; + }, + ); + } + + responseCount = beamResponseData.length; + const realResponseCount = beamResponseData.filter( + (response) => response.status !== "claimed", + ).length; + + if ( + (beamResponses.status !== 200 && + beamResponses.status !== 206) || + realResponseCount === this.sites.length + ) { + break; + } + } while (true); + } catch (err) { + if (err instanceof Error && err.name === "AbortError") { + console.log(`Aborting request ${this.currentTask}`); + } else { + console.error(err); + } + } + } +} diff --git a/packages/lib/src/classes/spot.ts b/packages/lib/src/classes/spot.ts index f4725106..62b32d7f 100644 --- a/packages/lib/src/classes/spot.ts +++ b/packages/lib/src/classes/spot.ts @@ -2,7 +2,8 @@ * TODO: document this class */ -import { responseStore } from "../stores/response"; +import type { Writable } from "svelte/store"; +// import { responseStore } from "../stores/response"; import type { ResponseStore } from "../types/backend"; import type { Site, SiteData, Status } from "../types/response"; @@ -23,8 +24,9 @@ export class Spot { constructor( private url: URL, private sites: Array, + private responseStore: Writable = responseStore, ) { - responseStore.subscribe( + this.responseStore.subscribe( (store: ResponseStore) => (this.storeCache = store), ); } @@ -94,7 +96,7 @@ export class Spot { changes.set(site, { status: status, data: body }); }); if (changes.size > 0) { - responseStore.update( + this.responseStore.update( (store: ResponseStore): ResponseStore => { changes.forEach((value, key) => { store.set(key, value); diff --git a/packages/lib/src/components/DataPasser.wc.svelte b/packages/lib/src/components/DataPasser.wc.svelte index 5d9676a4..c9e234cd 100644 --- a/packages/lib/src/components/DataPasser.wc.svelte +++ b/packages/lib/src/components/DataPasser.wc.svelte @@ -113,4 +113,12 @@ export const getAstAPI = (): AstTopLayer => { return buildAstFromQuery($queryStore); }; + + /** + * sets the response from the backend + * @param response the response from the backend + */ + export const setResponseAPI = (response: ResponseStore): void => { + responseStore.set(response); + }; diff --git a/packages/lib/src/components/Options.wc.svelte b/packages/lib/src/components/Options.wc.svelte index a3db079a..d21185e0 100644 --- a/packages/lib/src/components/Options.wc.svelte +++ b/packages/lib/src/components/Options.wc.svelte @@ -15,11 +15,16 @@ */ import { lensOptions } from "../stores/options"; import { catalogue } from "../stores/catalogue"; + import { measureStore } from "../stores/measures"; import type { Criteria } from "../types/treeData"; + import type { Measure } from "../types/backend"; + import type { LensOptions } from "../types/options"; - export let options: object = {}; + export let options: LensOptions = {}; export let catalogueData: Criteria[] = []; + export let measures: Measure[] = []; $: $lensOptions = options; $: $catalogue = catalogueData; + $: $measureStore = measures; diff --git a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte index 46d2866c..6305c6d7 100644 --- a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte +++ b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte @@ -20,23 +20,16 @@ catalogueKeyToResponseKeyMap, uiSiteMappingsStore, } from "../../stores/mappings"; - import type { Measure, BackendConfig } from "../../types/backend"; import { responseStore } from "../../stores/response"; + import { lensOptions } from "../../stores/options"; + import type { BackendOptions, SpotOption } from "../../types/backend"; export let title: string = "Search"; - export let backendConfig: BackendConfig = { - url: "http://localhost:8080", - backends: ["dktk-test", "mannheim"], - uiSiteMap: [ - ["dktk-test", "DKTK Test"], - ["mannheim", "Mannheim"], - ], - catalogueKeyToResponseKeyMap: [], - }; export let disabled: boolean = false; - export let measures: Measure[] = []; - export let backendMeasures: string = ""; + + $: options = $lensOptions?.backends as BackendOptions; + let controller: AbortController; /** @@ -45,28 +38,31 @@ * therefore it's a 2d array of strings which is converted to a map */ $: uiSiteMappingsStore.update((mappings) => { - backendConfig.uiSiteMap.forEach((site) => { - mappings.set(site[0], site[1]); + options?.spots.forEach((spot) => { + spot.uiSiteMap.forEach((site) => { + mappings.set(site[0], site[1]); + }); }); return mappings; }); $: catalogueKeyToResponseKeyMap.update((mappings) => { - backendConfig.catalogueKeyToResponseKeyMap.forEach((mapping) => { - mappings.set(mapping[0], mapping[1]); + options?.spots.forEach((spot) => { + spot.catalogueKeyToResponseKeyMap.forEach((mapping) => { + mappings.set(mapping[0], mapping[1]); + }); }); return mappings; }); - /** - * watches the measures for changes to populate the measureStore - */ - $: measureStore.set(measures); - /** * triggers a request to the backend via the spot class */ - const getResultsFromBackend = async (): void => { + const getResultsFromBackend = (): void => { + /** + * TODO emit event + */ + if (controller) { controller.abort(); } @@ -75,22 +71,29 @@ controller = new AbortController(); const ast = buildAstFromQuery($queryStore); - const cql = translateAstToCql(ast, false, backendMeasures); - const library = buildLibrary(`${cql}`); - const measure = buildMeasure( - library.url, - $measureStore.map((measureItem) => measureItem.measure), - ); - const query = { lang: "cql", lib: library, measure: measure }; + options.spots.forEach((spot: SpotOption) => { + /** + * TODO: add backend measures + */ + const cql = translateAstToCql(ast, false, "backend-measures"); - const backend = new Spot( - new URL(backendConfig.url), - backendConfig.backends, - ); + const library = buildLibrary(`${cql}`); + const measure = buildMeasure( + library.url, + $measureStore.map((measureItem) => measureItem.measure), + ); + const query = { lang: "cql", lib: library, measure: measure }; - backend.send(btoa(decodeURI(JSON.stringify(query))), controller); + const backend = new Spot( + new URL(spot.url), + spot.sites, + responseStore, + ); + console.log(backend); + backend.send(btoa(decodeURI(JSON.stringify(query))), controller); + }); queryModified.set(false); }; diff --git a/packages/lib/src/types/backend.ts b/packages/lib/src/types/backend.ts index 62974bc5..31c07692 100644 --- a/packages/lib/src/types/backend.ts +++ b/packages/lib/src/types/backend.ts @@ -6,11 +6,19 @@ export type Measure = { cql: string; }; -export type BackendConfig = { +export type ResponseStore = Map; + +export type SpotOption = { url: string; - backends: string[]; + sites: string[]; uiSiteMap: string[][]; catalogueKeyToResponseKeyMap: string[][]; }; - -export type ResponseStore = Map; +/** + * TODO: implement BlazeOption + */ +export type BlazeOption = null; +export type BackendOptions = { + spots: SpotOption[]; + blazes: BlazeOption[]; +}; From a980e77430118e089628071edc2c1aa81964dd95 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Mon, 4 Mar 2024 11:10:39 +0100 Subject: [PATCH 05/50] feat(measures): add measure configuration for multiple spots --- packages/demo/public/options.json | 2 + packages/demo/src/AppCCP.svelte | 21 ++++---- packages/lib/src/components/Options.wc.svelte | 4 +- .../buttons/SearchButtonComponenet.wc.svelte | 38 ++++++++++---- .../ast-to-cql-translator.ts | 15 ++---- packages/lib/src/helpers/cql-measure.ts | 5 +- packages/lib/src/stores/measures.ts | 8 +-- packages/lib/src/types/backend.ts | 51 ++++++++++++++++++- 8 files changed, 104 insertions(+), 40 deletions(-) diff --git a/packages/demo/public/options.json b/packages/demo/public/options.json index e9b379ef..bb97291a 100644 --- a/packages/demo/public/options.json +++ b/packages/demo/public/options.json @@ -168,6 +168,8 @@ "backends": { "spots": [ { + "name": "DKTK", + "backendMeasures": "DKTK_STRAT_DEF_IN_INITIAL_POPULATION", "url": "http://localhost:8080", "sites": [ "berlin", diff --git a/packages/demo/src/AppCCP.svelte b/packages/demo/src/AppCCP.svelte index 41e122d8..7302dd0b 100644 --- a/packages/demo/src/AppCCP.svelte +++ b/packages/demo/src/AppCCP.svelte @@ -24,16 +24,19 @@ }); const measures = [ - dktkPatientsMeasure, - dktkDiagnosisMeasure, - dktkSpecimenMeasure, - dktkProceduresMeasure, - dktkMedicationStatementsMeasure, - dktkHistologyMeasure, + { + name: "DKTK", + measures: [ + dktkPatientsMeasure, + dktkDiagnosisMeasure, + dktkSpecimenMeasure, + dktkProceduresMeasure, + dktkMedicationStatementsMeasure, + dktkHistologyMeasure, + ], + }, ]; - const backendMeasures = `DKTK_STRAT_DEF_IN_INITIAL_POPULATION`; - const catalogueText = { group: "Group", collapseButtonTitle: "Collapse Tree", @@ -94,7 +97,7 @@ noQueryMessage="Leere Suchanfrage: Sucht nach allen Ergebnissen." showQuery={true} /> - +
diff --git a/packages/lib/src/components/Options.wc.svelte b/packages/lib/src/components/Options.wc.svelte index d21185e0..1d3333e5 100644 --- a/packages/lib/src/components/Options.wc.svelte +++ b/packages/lib/src/components/Options.wc.svelte @@ -17,12 +17,12 @@ import { catalogue } from "../stores/catalogue"; import { measureStore } from "../stores/measures"; import type { Criteria } from "../types/treeData"; - import type { Measure } from "../types/backend"; + import type { MeasureStore } from "../types/backend"; import type { LensOptions } from "../types/options"; export let options: LensOptions = {}; export let catalogueData: Criteria[] = []; - export let measures: Measure[] = []; + export let measures: MeasureStore = {} as MeasureStore; $: $lensOptions = options; $: $catalogue = catalogueData; diff --git a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte index 6305c6d7..81055ac7 100644 --- a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte +++ b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte @@ -22,7 +22,13 @@ } from "../../stores/mappings"; import { responseStore } from "../../stores/response"; import { lensOptions } from "../../stores/options"; - import type { BackendOptions, SpotOption } from "../../types/backend"; + import type { + BackendOptions, + Measure, + MeasureItem, + MeasureOption, + SpotOption, + } from "../../types/backend"; export let title: string = "Search"; @@ -73,16 +79,30 @@ const ast = buildAstFromQuery($queryStore); options.spots.forEach((spot: SpotOption) => { - /** - * TODO: add backend measures - */ - const cql = translateAstToCql(ast, false, "backend-measures"); + const name = spot.name; + const measureItem: MeasureOption | undefined = $measureStore.find( + (measureStoreItem: MeasureOption) => + spot.name === measureStoreItem.name, + ); - const library = buildLibrary(`${cql}`); - const measure = buildMeasure( - library.url, - $measureStore.map((measureItem) => measureItem.measure), + if (measureItem === undefined) { + throw new Error( + `No measures found for backend ${name}. Please check the measures store.`, + ); + } + const measures: Measure[] = measureItem.measures.map( + (measureItem: MeasureItem) => measureItem.measure, + ); + + const cql = translateAstToCql( + ast, + false, + spot.backendMeasures, + measureItem.measures, ); + + const library = buildLibrary(`${cql}`); + const measure = buildMeasure(library.url, measures); const query = { lang: "cql", lib: library, measure: measure }; const backend = new Spot( diff --git a/packages/lib/src/cql-translator-service/ast-to-cql-translator.ts b/packages/lib/src/cql-translator-service/ast-to-cql-translator.ts index 494be428..4f8438c5 100644 --- a/packages/lib/src/cql-translator-service/ast-to-cql-translator.ts +++ b/packages/lib/src/cql-translator-service/ast-to-cql-translator.ts @@ -13,17 +13,7 @@ import { criterionMap, } from "./cqlquery-mappings"; import { getCriteria } from "../stores/catalogue"; -import type { Measure } from "../types/backend"; -import { measureStore } from "../stores/measures"; - -/** - * Get all cql from the project specific measures from the store - */ -let measuresCql: string[] = []; - -measureStore.subscribe((measures: Measure[]) => { - measuresCql = measures.map((measure) => measure.cql); -}); +import type { MeasureItem } from "../types/backend"; let codesystems: string[] = []; let criteria: string[]; @@ -32,6 +22,7 @@ export const translateAstToCql = ( query: AstTopLayer, returnOnlySingeltons: boolean = true, backendMeasures: string, + measures: MeasureItem[], ): string => { criteria = getCriteria("diagnosis"); @@ -66,7 +57,7 @@ export const translateAstToCql = ( cqlHeader + getCodesystems() + "context Patient\n" + - measuresCql.join("") + + measures.map((measureItem: MeasureItem) => measureItem.cql).join("") + singletons ); }; diff --git a/packages/lib/src/helpers/cql-measure.ts b/packages/lib/src/helpers/cql-measure.ts index d93b3c10..21f3b59a 100644 --- a/packages/lib/src/helpers/cql-measure.ts +++ b/packages/lib/src/helpers/cql-measure.ts @@ -1,4 +1,5 @@ import { v4 as uuidv4 } from "uuid"; +import type { Measure } from "../types/backend"; type BuildLibraryReturn = { resourceType: string; @@ -57,12 +58,12 @@ type BuildMeasureReturn = { code: string; }[]; }; - group: object[]; + group: Measure[]; }; export const buildMeasure = ( libraryUrl: string, - measures: object[], + measures: Measure[], ): BuildMeasureReturn => { const measureId = uuidv4(); return { diff --git a/packages/lib/src/stores/measures.ts b/packages/lib/src/stores/measures.ts index b078c7cb..0684b461 100644 --- a/packages/lib/src/stores/measures.ts +++ b/packages/lib/src/stores/measures.ts @@ -1,9 +1,9 @@ -import { writable } from 'svelte/store'; -import type { Measure } from '../types/backend'; +import { writable } from "svelte/store"; +import type { MeasureStore } from "../types/backend"; /** - * Store to hold the measures + * Store to hold the measures * populated by the search button */ -export const measureStore = writable([]); \ No newline at end of file +export const measureStore = writable(); diff --git a/packages/lib/src/types/backend.ts b/packages/lib/src/types/backend.ts index 31c07692..03ea6845 100644 --- a/packages/lib/src/types/backend.ts +++ b/packages/lib/src/types/backend.ts @@ -1,14 +1,61 @@ import type { Site } from "./response"; -export type Measure = { +export type MeasureItem = { key: string; - measure: object; + measure: Measure; cql: string; }; +export type Measure = { + code: { + text: string; + }; + extension: [ + { + url: string; + valueCode: string; + }, + ]; + population: [ + { + code: { + coding: [ + { + system: string; + code: string; + }, + ]; + }; + criteria: { + language: string; + expression: string; + }; + }, + ]; + stratifier: [ + { + code: { + text: string; + }; + criteria: { + language: string; + expression: string; + }; + }, + ]; +}; + +export type MeasureOption = { + name: string; + measures: MeasureItem[]; +}; + +export type MeasureStore = MeasureOption[]; export type ResponseStore = Map; export type SpotOption = { + name: string; + backendMeasures: string; url: string; sites: string[]; uiSiteMap: string[][]; From 06ce3ac11ffd710ed2e88c2e032159a89ab3f4b8 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Wed, 6 Mar 2024 16:13:54 +0100 Subject: [PATCH 06/50] feat(backends): add support for project hosted backends - add additional spot to ccp App - search triggers event which contains store update function and ast --- packages/demo/public/options.json | 45 ++- packages/demo/src/AppCCP.svelte | 64 ++++ .../demo/src/AppFragmentDevelopment.svelte | 5 +- .../src/backends/ast-to-cql-translator.ts | 353 ++++++++++++++++++ packages/demo/src/backends/cql-measure.ts | 92 +++++ .../demo/src/backends/cqlquery-mappings.ts | 313 ++++++++++++++++ packages/demo/src/backends/spot.ts | 49 +-- packages/lib/src/classes/spot.ts | 44 +-- .../lib/src/components/DataPasser.wc.svelte | 21 +- .../buttons/SearchButtonComponenet.wc.svelte | 49 ++- packages/lib/src/stores/catalogue.ts | 2 +- packages/lib/src/stores/query.ts | 7 +- packages/lib/src/stores/response.ts | 25 ++ .../src/{interfaces => types}/DataPasser.d.ts | 10 +- packages/lib/src/types/backend.ts | 9 + packages/lib/src/types/response.ts | 9 + 16 files changed, 998 insertions(+), 99 deletions(-) create mode 100644 packages/demo/src/backends/ast-to-cql-translator.ts create mode 100644 packages/demo/src/backends/cql-measure.ts create mode 100644 packages/demo/src/backends/cqlquery-mappings.ts rename packages/lib/src/{interfaces => types}/DataPasser.d.ts (67%) diff --git a/packages/demo/public/options.json b/packages/demo/public/options.json index bb97291a..30c774cb 100644 --- a/packages/demo/public/options.json +++ b/packages/demo/public/options.json @@ -173,7 +173,6 @@ "url": "http://localhost:8080", "sites": [ "berlin", - "berlin-test", "bonn", "dresden", "essen", @@ -285,6 +284,50 @@ "75186-7" ] ] + }, + { + "name": "DKTK", + "backendMeasures": "DKTK_STRAT_DEF_IN_INITIAL_POPULATION", + "url": "http://localhost:8080", + "sites": [ + "berlin-test" + ], + "uiSiteMap": [ + [ + "berlin-test", + "Berlin Test" + ] + ], + "catalogueKeyToResponseKeyMap": [ + [ + "gender", + "Gender" + ], + [ + "age_at_diagnosis", + "Age" + ], + [ + "diagnosis", + "diagnosis" + ], + [ + "medicationStatements", + "MedicationType" + ], + [ + "sample_kind", + "sample_kind" + ], + [ + "therapy_of_tumor", + "ProcedureType" + ], + [ + "75186-7", + "75186-7" + ] + ] } ], "blazes": [] diff --git a/packages/demo/src/AppCCP.svelte b/packages/demo/src/AppCCP.svelte index 7302dd0b..837e9618 100644 --- a/packages/demo/src/AppCCP.svelte +++ b/packages/demo/src/AppCCP.svelte @@ -1,4 +1,6 @@
@@ -240,3 +303,4 @@ catalogueData={mockCatalogueData} {measures} /> + diff --git a/packages/demo/src/AppFragmentDevelopment.svelte b/packages/demo/src/AppFragmentDevelopment.svelte index b60d82b2..88821a6c 100644 --- a/packages/demo/src/AppFragmentDevelopment.svelte +++ b/packages/demo/src/AppFragmentDevelopment.svelte @@ -1,4 +1,5 @@ diff --git a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte index 81055ac7..8ab7f097 100644 --- a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte +++ b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte @@ -20,7 +20,7 @@ catalogueKeyToResponseKeyMap, uiSiteMappingsStore, } from "../../stores/mappings"; - import { responseStore } from "../../stores/response"; + import { responseStore, updateResponseStore } from "../../stores/response"; import { lensOptions } from "../../stores/options"; import type { BackendOptions, @@ -29,6 +29,8 @@ MeasureOption, SpotOption, } from "../../types/backend"; + import type { AstTopLayer } from "../../types/ast"; + import type { Site } from "../../types/response"; export let title: string = "Search"; @@ -62,13 +64,12 @@ }); /** - * triggers a request to the backend via the spot class + * Triggers a request to the backend. + * Multiple spots and blazes can be configured in lens options. + * Emits the ast and the updateResponseStore function to the project + * for running the query on other backends as well. */ const getResultsFromBackend = (): void => { - /** - * TODO emit event - */ - if (controller) { controller.abort(); } @@ -105,17 +106,39 @@ const measure = buildMeasure(library.url, measures); const query = { lang: "cql", lib: library, measure: measure }; - const backend = new Spot( - new URL(spot.url), - spot.sites, - responseStore, - ); - console.log(backend); + const backend = new Spot(new URL(spot.url), spot.sites); - backend.send(btoa(decodeURI(JSON.stringify(query))), controller); + backend.send( + btoa(decodeURI(JSON.stringify(query))), + updateResponseStore, + controller, + ); }); + emitEvent(ast); queryModified.set(false); }; + + interface QueryEvent extends Event { + detail: { + ast: AstTopLayer; + updateResponse: (response: Map) => void; + abortController?: AbortController; + }; + } + /** + * Emits the ast and the updateResponseStore function to the project + * @param ast the ast to be emitted + */ + const emitEvent = (ast: AstTopLayer): void => { + const event: QueryEvent = new CustomEvent("emit-lens-query", { + detail: { + ast: ast, + updateResponse: updateResponseStore, + abortController: controller, + }, + }); + window.dispatchEvent(event); + }; + {#if deleteUrl} + delete icon + {:else} + ✕ + {/if} + From 3af7666d2d70b6416efb6fb5786eda37b24eb783 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Mon, 15 Apr 2024 11:07:42 +0200 Subject: [PATCH 09/50] feat(searchbar): fix css of delete icon --- packages/demo/src/ccp.css | 21 ++++++++----------- .../buttons/StoreDeleteButtonComponent.svelte | 6 +++++- packages/lib/src/styles/searchbars.css | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/demo/src/ccp.css b/packages/demo/src/ccp.css index e58c5539..ea49cb5b 100644 --- a/packages/demo/src/ccp.css +++ b/packages/demo/src/ccp.css @@ -307,20 +307,18 @@ lens-search-bar-multiple::part(info-button-dialogue) { text-align: left; } -lens-search-bar::part(delete-button-icon), -lens-search-bar-multiple::part(delete-button-icon) { - filter: brightness(0) saturate(100%) invert(12%) sepia(100%) saturate(5657%) hue-rotate(357deg) brightness(90%) contrast(128%); +lens-search-bar::part(delete-button-icon-item), +lens-search-bar-multiple::part(delete-button-icon-item) { + filter: invert(41%) sepia(43%) saturate(4610%) hue-rotate(357deg) brightness(96%) contrast(90%); transform: translate(-1px, -1px); - width: 21px; -} - -lens-search-bar-multiple::part(query-delete-button-item) { - border-color: var(--white); + width: 20px; } -lens-search-bar-multiple::part(query-delete-button-group) { - background-color: var(--white); - border-color: var(--white); +lens-search-bar::part(delete-button-icon-group), +lens-search-bar-multiple::part(delete-button-icon) { + filter: invert(41%) sepia(43%) saturate(4610%) hue-rotate(357deg) brightness(96%) contrast(90%); + transform: translate(0px, 2px); + width: 20px; } lens-search-button::part(lens-search-button) { @@ -333,7 +331,6 @@ lens-search-button::part(lens-search-button):hover { lens-search-button::part(lens-search-button):active { background-color: #003d7e; - transform: translateX(1px); } /** diff --git a/packages/lib/src/components/buttons/StoreDeleteButtonComponent.svelte b/packages/lib/src/components/buttons/StoreDeleteButtonComponent.svelte index e0464992..6baff648 100644 --- a/packages/lib/src/components/buttons/StoreDeleteButtonComponent.svelte +++ b/packages/lib/src/components/buttons/StoreDeleteButtonComponent.svelte @@ -40,7 +40,11 @@ on:click={deleteItem} > {#if deleteUrl} - delete icon + delete icon {:else} ✕ {/if} diff --git a/packages/lib/src/styles/searchbars.css b/packages/lib/src/styles/searchbars.css index 4d45a723..09b8412a 100644 --- a/packages/lib/src/styles/searchbars.css +++ b/packages/lib/src/styles/searchbars.css @@ -204,7 +204,7 @@ lens-search-bar-multiple::part(query-delete-button-item) { position: absolute; top: -6px; right: -10px; - border: solid 2px var(--white); + border: solid 1px var(--white); } lens-search-bar::part(query-delete-button-group), From 829b605121504e1263f37714a4222e2d32d1b73f Mon Sep 17 00:00:00 2001 From: Zachariah Frank Date: Mon, 15 Apr 2024 14:19:29 +0200 Subject: [PATCH 10/50] fix(delete-icon): css in chip sub-queries --- packages/demo/public/delete_icon.svg | 4 ++-- packages/demo/src/ccp.css | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/demo/public/delete_icon.svg b/packages/demo/public/delete_icon.svg index e03a318c..917e1d65 100644 --- a/packages/demo/public/delete_icon.svg +++ b/packages/demo/public/delete_icon.svg @@ -1,4 +1,4 @@ - - \ No newline at end of file + + diff --git a/packages/demo/src/ccp.css b/packages/demo/src/ccp.css index ea49cb5b..34dbaa7d 100644 --- a/packages/demo/src/ccp.css +++ b/packages/demo/src/ccp.css @@ -318,9 +318,20 @@ lens-search-bar::part(delete-button-icon-group), lens-search-bar-multiple::part(delete-button-icon) { filter: invert(41%) sepia(43%) saturate(4610%) hue-rotate(357deg) brightness(96%) contrast(90%); transform: translate(0px, 2px); +} + +lens-search-bar::part(delete-button-icon), +lens-search-bar-multiple::part(delete-button-icon-value) { + transform: translate(-1px, -1px); width: 20px; } +lens-search-bar::part(delete-button-icon):hover, +lens-search-bar-multiple::part(delete-button-icon-value):hover { + filter: invert(38%) sepia(78%) saturate(1321%) hue-rotate(352deg) brightness(92%) contrast(99%); +} + + lens-search-button::part(lens-search-button) { background-color: var(--light-blue); } From 76abf0f4db65dd89b3a00b5e937c5982c5e74733 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 25 Apr 2024 11:06:44 +0200 Subject: [PATCH 11/50] feat(spot): add rusty spot --- docker-compose.dev.yml | 10 ++++--- packages/demo/public/options.json | 47 +------------------------------ 2 files changed, 7 insertions(+), 50 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index db1ea38c..53671a46 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -21,11 +21,13 @@ services: spot: image: samply/rustyspot:main ports: - - 8080:8080 + - 8055:8055 environment: - BEAM_SECRET: "${LOCAL_BEAM_SECRET_SPOT}" + RUST_LOG: "info" + CORS_ORIGIN: http://localhost:5173 + BEAM_SECRET: "${LOCAL_BEAM_SECRET_FOCUS}" BEAM_PROXY_URL: http://beam-proxy:8081 - BEAM_APP_ID: "spot.${LOCAL_BEAM_ID}.${BROKER_HOST}" + BEAM_APP_ID: "focus.${LOCAL_BEAM_ID}.${BROKER_HOST}" depends_on: - "beam-proxy" profiles: @@ -36,7 +38,7 @@ services: environment: BROKER_URL: https://${BROKER_HOST} PROXY_ID: ${LOCAL_BEAM_ID}.${BROKER_HOST} - APP_spot_KEY: ${LOCAL_BEAM_SECRET_SPOT} + APP_focus_KEY: ${LOCAL_BEAM_SECRET_FOCUS} PRIVKEY_FILE: /run/secrets/proxy.pem ALL_PROXY: ${http_proxy} secrets: diff --git a/packages/demo/public/options.json b/packages/demo/public/options.json index 464b49df..0fb40a7b 100644 --- a/packages/demo/public/options.json +++ b/packages/demo/public/options.json @@ -194,10 +194,9 @@ { "name": "DKTK", "backendMeasures": "DKTK_STRAT_DEF_IN_INITIAL_POPULATION", - "url": "http://localhost:8080", + "url": "http://localhost:8055", "sites": [ "berlin", - "bonn", "dresden", "essen", "frankfurt", @@ -308,50 +307,6 @@ "75186-7" ] ] - }, - { - "name": "DKTK", - "backendMeasures": "DKTK_STRAT_DEF_IN_INITIAL_POPULATION", - "url": "http://localhost:8080", - "sites": [ - "berlin-test" - ], - "uiSiteMap": [ - [ - "berlin-test", - "Berlin Test" - ] - ], - "catalogueKeyToResponseKeyMap": [ - [ - "gender", - "Gender" - ], - [ - "age_at_diagnosis", - "Age" - ], - [ - "diagnosis", - "diagnosis" - ], - [ - "medicationStatements", - "MedicationType" - ], - [ - "sample_kind", - "sample_kind" - ], - [ - "therapy_of_tumor", - "ProcedureType" - ], - [ - "75186-7", - "75186-7" - ] - ] } ], "blazes": [ From cab05cda6315b872ee34b987bb6697e691851c92 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 16 May 2024 12:53:28 +0200 Subject: [PATCH 12/50] feat(spot): add project specific backend configuration currently implemented as spot in the fragment development project --- docker-compose.yml | 2 + packages/demo/src/AppCCP.svelte | 3 + .../demo/src/AppFragmentDevelopment.svelte | 175 +++++++++--------- .../src/backends/ast-to-cql-translator.ts | 29 ++- .../demo/src/backends/cqlquery-mappings.ts | 116 ++++++++++++ packages/demo/src/backends/spot.ts | 106 +++++------ packages/demo/src/fragment-development.css | 2 +- packages/demo/src/main.ts | 8 +- packages/lib/src/classes/spot.ts | 13 +- .../lib/src/components/DataPasser.wc.svelte | 130 ++++++------- .../buttons/SearchButtonComponenet.wc.svelte | 2 +- packages/lib/src/stores/response.ts | 8 +- packages/lib/src/styles/searchbars.css | 48 ++++- 13 files changed, 414 insertions(+), 228 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1c06385d..7bf498cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -55,6 +55,8 @@ services: spot: image: samply/rustyspot:main + ports: + - "8055:8055" environment: HTTP_PROXY: ${http_proxy} HTTPS_PROXY: ${https_proxy} diff --git a/packages/demo/src/AppCCP.svelte b/packages/demo/src/AppCCP.svelte index c0fb989e..4755726d 100644 --- a/packages/demo/src/AppCCP.svelte +++ b/packages/demo/src/AppCCP.svelte @@ -44,6 +44,9 @@ }, ]; + /** + * move to config file + */ const catalogueText = { group: "Group", collapseButtonTitle: "Collapse Tree", diff --git a/packages/demo/src/AppFragmentDevelopment.svelte b/packages/demo/src/AppFragmentDevelopment.svelte index 1387a867..f02dd924 100644 --- a/packages/demo/src/AppFragmentDevelopment.svelte +++ b/packages/demo/src/AppFragmentDevelopment.svelte @@ -1,6 +1,12 @@
@@ -194,11 +188,7 @@

Search Button

- +

Result Summary Bar

@@ -208,7 +198,7 @@

Result Table

- +

Result Pie Chart

@@ -243,4 +233,5 @@
- + + diff --git a/packages/demo/src/backends/ast-to-cql-translator.ts b/packages/demo/src/backends/ast-to-cql-translator.ts index 8cce0085..6b6a8148 100644 --- a/packages/demo/src/backends/ast-to-cql-translator.ts +++ b/packages/demo/src/backends/ast-to-cql-translator.ts @@ -110,8 +110,11 @@ const getSingleton = (criterion: AstBottomLayerValue): string => { if (myCQL) { switch (myCriterion.type) { case "gender": + case "BBMRI_gender": case "histology": case "conditionValue": + case "BBMRI_conditionValue": + case "BBMRI_conditionSampleDiagnosis": case "conditionBodySite": case "conditionLocalization": case "observation": @@ -122,6 +125,8 @@ const getSingleton = (criterion: AstBottomLayerValue): string => { case "procedureResidualstatus": case "medicationStatement": case "specimen": + case "BBMRI_specimen": + case "BBMRI_hasSpecimen": case "hasSpecimen": case "Organization": case "observationMolecularMarkerName": @@ -134,7 +139,10 @@ const getSingleton = (criterion: AstBottomLayerValue): string => { case "TNMc": { if (typeof criterion.value === "string") { // TODO: Check if we really need to do this or we can somehow tell cql to do that expansion it self - if (criterion.value.slice(-1) === "%") { + if ( + criterion.value.slice(-1) === "%" && + criterion.value.length == 5 + ) { const mykey = criterion.value.slice(0, -2); if (criteria != undefined) { const expandedValues = criteria.filter( @@ -147,6 +155,25 @@ const getSingleton = (criterion: AstBottomLayerValue): string => { value: expandedValues, }); } + } else if ( + criterion.value.slice(-1) === "%" && + criterion.value.length == 6 + ) { + const mykey = criterion.value.slice(0, -1); + if (criteria != undefined) { + const expandedValues = criteria.filter( + (value) => value.startsWith(mykey), + ); + expandedValues.push( + criterion.value.slice(0, 5), + ); + expression += getSingleton({ + key: criterion.key, + type: criterion.type, + system: criterion.system, + value: expandedValues, + }); + } } else { expression += substituteCQLExpression( criterion.key, diff --git a/packages/demo/src/backends/cqlquery-mappings.ts b/packages/demo/src/backends/cqlquery-mappings.ts index f6b94452..12bcac63 100644 --- a/packages/demo/src/backends/cqlquery-mappings.ts +++ b/packages/demo/src/backends/cqlquery-mappings.ts @@ -63,6 +63,21 @@ export const alias = new Map([ "http://dktk.dkfz.de/fhir/onco/core/CodeSystem/TNMmSymbolCS", ], ["molecularMarker", "http://www.genenames.org"], + + ["BBMRI_icd10", "http://hl7.org/fhir/sid/icd-10"], + ["BBMRI_icd10gm", "http://fhir.de/CodeSystem/dimdi/icd-10-gm"], + [ + "BBMRI_SampleMaterialType", + "https://fhir.bbmri.de/CodeSystem/SampleMaterialType", + ], //specimentype + [ + "BBMRI_StorageTemperature", + "https://fhir.bbmri.de/CodeSystem/StorageTemperature", + ], + [ + "BBMRI_SmokingStatus", + "http://hl7.org/fhir/uv/ips/ValueSet/current-smoking-status-uv-ips", + ], ]); export const cqltemplate = new Map([ @@ -192,6 +207,69 @@ export const cqltemplate = new Map([ "(exists ([Observation: Code '21908-9' from loinc] O where O.value.coding.code contains '{{C}}')) or (exists ([Observation: Code '21902-2' from loinc] O where O.value.coding.code contains '{{C}}'))", ], ["histology", "exists from [Observation: Code '59847-4' from loinc] O\n"], + + ["BBMRI_gender", "Patient.gender"], + [ + "BBMRI_conditionSampleDiagnosis", + "((exists[Condition: Code '{{C}}' from {{A1}}]) or (exists[Condition: Code '{{C}}' from {{A2}}])) or (exists from [Specimen] S where (S.extension.where(url='https://fhir.bbmri.de/StructureDefinition/SampleDiagnosis').value.coding.code contains '{{C}}'))", + ], + ["BBMRI_conditionValue", "exists [Condition: Code '{{C}}' from {{A1}}]"], + [ + "BBMRI_conditionRangeDate", + "exists from [Condition] C\nwhere FHIRHelpers.ToDateTime(C.onset) between {{D1}} and {{D2}}", + ], + [ + "BBMRI_conditionRangeAge", + "exists from [Condition] C\nwhere AgeInYearsAt(FHIRHelpers.ToDateTime(C.onset)) between Ceiling({{D1}}) and Ceiling({{D2}})", + ], + ["BBMRI_age", "AgeInYears() between Ceiling({{D1}}) and Ceiling({{D2}})"], + [ + "BBMRI_observation", + "exists from [Observation: Code '{{K}}' from {{A1}}] O\nwhere O.value.coding.code contains '{{C}}'", + ], + [ + "BBMRI_observationSmoker", + "exists from [Observation: Code '72166-2' from {{A1}}] O\nwhere O.value.coding.code contains '{{C}}'", + ], + [ + "BBMRI_observationRange", + "exists from [Observation: Code '{{K}}' from {{A1}}] O\nwhere O.value between {{D1}} and {{D2}}", + ], + [ + "BBMRI_observationBodyWeight", + "exists from [Observation: Code '29463-7' from {{A1}}] O\nwhere ((O.value as Quantity) < {{D1}} 'kg' and (O.value as Quantity) > {{D2}} 'kg')", + ], + [ + "BBMRI_observationBMI", + "exists from [Observation: Code '39156-5' from {{A1}}] O\nwhere ((O.value as Quantity) < {{D1}} 'kg/m2' and (O.value as Quantity) > {{D2}} 'kg/m2')", + ], + ["BBMRI_hasSpecimen", "exists [Specimen]"], + ["BBMRI_specimen", "exists [Specimen: Code '{{C}}' from {{A1}}]"], + ["BBMRI_retrieveSpecimenByType", "(S.type.coding.code contains '{{C}}')"], + [ + "BBMRI_retrieveSpecimenByTemperature", + "(S.extension.where(url='https://fhir.bbmri.de/StructureDefinition/StorageTemperature').value.coding.code contains '{{C}}')", + ], + [ + "BBMRI_retrieveSpecimenBySamplingDate", + "(FHIRHelpers.ToDateTime(S.collection.collected) between {{D1}} and {{D2}})", + ], + [ + "BBMRI_retrieveSpecimenByFastingStatus", + "(S.collection.fastingStatus.coding.code contains '{{C}}')", + ], + [ + "BBMRI_samplingDate", + "exists from [Specimen] S\nwhere FHIRHelpers.ToDateTime(S.collection.collected) between {{D1}} and {{D2}}", + ], + [ + "BBMRI_fastingStatus", + "exists from [Specimen] S\nwhere S.collection.fastingStatus.coding.code contains '{{C}}'", + ], + [ + "BBMRI_storageTemperature", + "exists from [Specimen] S where (S.extension.where(url='https://fhir.bbmri.de/StructureDefinition/StorageTemperature').value.coding contains Code '{{C}}' from {{A1}})", + ], ]); export const criterionMap = new Map( @@ -309,5 +387,43 @@ export const criterionMap = new Map( ["75186-7", { type: "observation", alias: ["loinc", "vitalstatuscs"] }], //Vitalstatus //["Organization", {type: "Organization"}], ["Organization", { type: "department" }], + + ["BBMRI_gender", { type: "BBMRI_gender" }], + [ + "BBMRI_diagnosis", + { + type: "BBMRI_conditionSampleDiagnosis", + alias: ["BBMRI_icd10", "BBMRI_icd10gm"], + }, + ], + [ + "BBMRI_body_weight", + { type: "BBMRI_observationBodyWeight", alias: ["loinc"] }, + ], //Body weight + ["BBMRI_bmi", { type: "BBMRI_observationBMI", alias: ["loinc"] }], //BMI + [ + "BBMRI_smoking_status", + { type: "BBMRI_observationSmoker", alias: ["loinc"] }, + ], //Smoking habit + ["BBMRI_donor_age", { type: "BBMRI_age" }], + ["BBMRI_date_of_diagnosis", { type: "BBMRI_conditionRangeDate" }], + [ + "BBMRI_sample_kind", + { type: "BBMRI_specimen", alias: ["BBMRI_SampleMaterialType"] }, + ], + [ + "BBMRI_storage_temperature", + { + type: "BBMRI_storageTemperature", + alias: ["BBMRI_StorageTemperature"], + }, + ], + ["BBMRI_pat_with_samples", { type: "BBMRI_hasSpecimen" }], + ["BBMRI_diagnosis_age_donor", { type: "BBMRI_conditionRangeAge" }], + [ + "BBMRI_fasting_status", + { type: "BBMRI_fastingStatus", alias: ["loinc"] }, + ], + ["BBMRI_sampling_date", { type: "BBMRI_samplingDate" }], ], ); diff --git a/packages/demo/src/backends/spot.ts b/packages/demo/src/backends/spot.ts index 0956ec7d..fa1f0d38 100644 --- a/packages/demo/src/backends/spot.ts +++ b/packages/demo/src/backends/spot.ts @@ -3,11 +3,10 @@ */ import type { - Site, + ResponseStore, SiteData, Status, BeamResult, - ResponseStore, } from "../../../../dist/types"; export class Spot { @@ -21,22 +20,30 @@ export class Spot { /** * sends the query to beam and updates the store with the results * @param query the query as base64 encoded string - * @param updateResponseStore the function to update the response store + * @param updateResponse the function to update the response store * @param controller the abort controller to cancel the request */ async send( query: string, - updateResponseStore: (response: ResponseStore) => void, - controller?: AbortController, + updateResponse: (response: ResponseStore) => void, + controller: AbortController, ): Promise { try { + this.currentTask = crypto.randomUUID(); const beamTaskResponse = await fetch( - `${this.url}tasks?sites=${this.sites.toString()}`, + `${this.url}beam?sites=${this.sites.toString()}`, { method: "POST", + headers: { + "Content-Type": "application/json", + }, credentials: import.meta.env.PROD ? "include" : "omit", - body: query, - signal: controller?.signal, + body: JSON.stringify({ + id: this.currentTask, + sites: this.sites, + query: query, + }), + signal: controller.signal, }, ); if (!beamTaskResponse.ok) { @@ -46,60 +53,49 @@ export class Spot { ); throw new Error(`Unable to create new beam task.`); } - this.currentTask = (await beamTaskResponse.json()).id; - - let responseCount: number = 0; - - do { - const beamResponses: Response = await fetch( - `${this.url}tasks/${this.currentTask}?wait_count=${responseCount + 1}`, - { - credentials: import.meta.env.PROD ? "include" : "omit", - signal: controller?.signal, - }, - ); - if (!beamResponses.ok) { - const error: string = await beamResponses.text(); - console.debug( - `Received ${beamResponses.status} with message ${error}`, - ); - throw new Error( - `Error then retrieving responses from Beam. Abborting requests ...`, - ); - } + console.info(`Created new Beam Task with id ${this.currentTask}`); - const beamResponseData: Array = - await beamResponses.json(); + const eventSource = new EventSource( + `${this.url.toString()}beam/${this.currentTask}?wait_count=${this.sites.length}`, + { + withCredentials: true, + }, + ); - const changes = new Map(); - beamResponseData.forEach((response: BeamResult) => { - if (response.task !== this.currentTask) return; - const site: string = response.from.split(".")[1]; - const status: Status = response.status; - const body: SiteData = - status === "succeeded" - ? JSON.parse(atob(response.body)) - : null; + /** + * Listenes to the new_result event from beam and updates the response store + */ + eventSource.addEventListener("new_result", (message) => { + const response: BeamResult = JSON.parse(message.data); + if (response.task !== this.currentTask) return; + const site: string = response.from.split(".")[1]; + const status: Status = response.status; + const body: SiteData = + status === "succeeded" + ? JSON.parse(atob(response.body)) + : null; - changes.set(site, { status: status, data: body }); + const parsedResponse: ResponseStore = new Map().set(site, { + status: status, + data: body, }); + updateResponse(parsedResponse); + }); - updateResponseStore(changes); - - responseCount = beamResponseData.length; - const realResponseCount = beamResponseData.filter( - (response) => response.status !== "claimed", - ).length; + // read error events from beam + eventSource.addEventListener("error", (message) => { + console.error(`Beam returned error ${message}`); + eventSource.close(); + }); - if ( - (beamResponses.status !== 200 && - beamResponses.status !== 206) || - realResponseCount === this.sites.length - ) { - break; - } - } while (true); + // event source in javascript throws an error then the event source is closed by backend + eventSource.onerror = () => { + console.info( + `Querying results from sites for task ${this.currentTask} finished.`, + ); + eventSource.close(); + }; } catch (err) { if (err instanceof Error && err.name === "AbortError") { console.log(`Aborting request ${this.currentTask}`); diff --git a/packages/demo/src/fragment-development.css b/packages/demo/src/fragment-development.css index 7ed33451..148490e3 100644 --- a/packages/demo/src/fragment-development.css +++ b/packages/demo/src/fragment-development.css @@ -1,4 +1,4 @@ -@import "../../../node_modules/@samply/lens/dist/style.css"; +/* @import "../../../node_modules/@samply/lens/dist/style.css"; */ @import "../../lib/src/styles/index.css"; /** diff --git a/packages/demo/src/main.ts b/packages/demo/src/main.ts index 2fc0469d..afed9a84 100644 --- a/packages/demo/src/main.ts +++ b/packages/demo/src/main.ts @@ -5,11 +5,11 @@ import "../../lib"; -// import "./fragment-development.css"; -// import App from "./AppFragmentDevelopment.svelte"; +import "./fragment-development.css"; +import App from "./AppFragmentDevelopment.svelte"; -import "./ccp.css"; -import App from "./AppCCP.svelte"; +// import "./ccp.css"; +// import App from "./AppCCP.svelte"; // import App from './AppBBMRI.svelte' // import './bbmri.css' diff --git a/packages/lib/src/classes/spot.ts b/packages/lib/src/classes/spot.ts index efca31fa..2de0c842 100644 --- a/packages/lib/src/classes/spot.ts +++ b/packages/lib/src/classes/spot.ts @@ -4,7 +4,6 @@ import type { SiteData, Status, BeamResult } from "../types/response"; import type { ResponseStore } from "../types/backend"; -import { responseStore } from "../stores/response"; export class Spot { private currentTask!: string; @@ -53,6 +52,9 @@ export class Spot { console.info(`Created new Beam Task with id ${this.currentTask}`); + /** + * Listenes to the new_result event from beam and updates the response store + */ const eventSource = new EventSource( `${this.url.toString()}beam/${this.currentTask}?wait_count=${this.sites.length}`, { @@ -61,7 +63,6 @@ export class Spot { ); eventSource.addEventListener("new_result", (message) => { const response: BeamResult = JSON.parse(message.data); - console.log("response", response); if (response.task !== this.currentTask) return; const site: string = response.from.split(".")[1]; const status: Status = response.status; @@ -70,11 +71,11 @@ export class Spot { ? JSON.parse(atob(response.body)) : null; - // updateResponse(changes); - responseStore.update((store: ResponseStore): ResponseStore => { - store.set(site, { status: status, data: body }); - return store; + const parsedResponse: ResponseStore = new Map().set(site, { + status: status, + data: body, }); + updateResponse(parsedResponse); }); // read error events from beam diff --git a/packages/lib/src/components/DataPasser.wc.svelte b/packages/lib/src/components/DataPasser.wc.svelte index 4a25b8c6..2b258709 100644 --- a/packages/lib/src/components/DataPasser.wc.svelte +++ b/packages/lib/src/components/DataPasser.wc.svelte @@ -19,6 +19,10 @@ import type { ResponseStore } from "../types/backend"; import type { AstTopLayer } from "../types/ast"; + /** + * Getters + */ + /** * returns the query store to the library user * @returns the query store @@ -28,25 +32,61 @@ }; /** - * lets the library user add a single stratifier to the query store - * @param label the value of the stratifier (e.g. "C31") - * @param catalogueGroupCode the code of the group where the stratifier is located (e.g. "diagnosis") - * @param queryGroupIndex the index of the query group where the stratifier should be added + * returns the response from the backend to the library user + * @returns the response from the backend */ + export const getResponseAPI = (): ResponseStore => { + return $responseStore; + }; - interface addStratifierToQueryAPIParams { - label: string; - catalogueGroupCode: string; - groupRange?: number; - queryGroupIndex?: number; - } + /** + * returns the AST to the library user + * @returns the AST + */ + export const getAstAPI = (): AstTopLayer => { + return buildAstFromQuery($queryStore); + }; + /** + * sets the response from the backend if any changes in status are detected + * @param response the response from the backend + */ + export const updateResponseStoreAPI = (response: ResponseStore): void => { + updateResponseStore(response); + }; + + /** + * returns the catalogue to the library user + * @param category the category name (e.g. "diagnosis") + * @returns array of strings containing the bottom level items' keys + */ + export const getCriteriaAPI = (category: string): string[] => { + return getCriteria(category); + }; + + /** + * Setters + */ + + /** + * lets the library user add a single stratifier to the query store + * @param params the parameters for the function + * @param params.label the value of the stratifier (e.g. "C31") + * @param params.catalogueGroupCode the code of the group where the stratifier is located (e.g. "diagnosis") + * @param params.groupRange of the numerical groups in charts + * @param params.queryGroupIndex the index of the query group where the stratifier should be added + */ export const addStratifierToQueryAPI = ({ label, catalogueGroupCode, groupRange, queryGroupIndex, - }: addStratifierToQueryAPIParams): void => { + }: { + label: string; + catalogueGroupCode: string; + groupRange?: number; + queryGroupIndex?: number; + }): void => { addStratifier({ label, catalogueGroupCode, @@ -58,76 +98,40 @@ /** * removes a query item from the query store - * @param queryObject the query object that should be removed - * @param queryGroupIndex the index of the query group where the stratifier should be removed + * @param params the parameters for the function + * @param params.queryObject the query object that should be removed + * @param params.queryGroupIndex the index of the query group where the stratifier should be removed */ - - interface RemoveItemFromQueryAPIParams { - queryObject: QueryItem; - queryGroupIndex?: number; - } - export const removeItemFromQuyeryAPI = ({ queryObject, queryGroupIndex = 0, - }: RemoveItemFromQueryAPIParams): void => { + }: { + queryObject: QueryItem; + queryGroupIndex?: number; + }): void => { removeItemFromQuery(queryObject, queryGroupIndex); }; /** * removes the value of a query item from the query store - * @param queryItem the query item from which the value should be removed from - * @param value the value that should be removed + * @param params the parameters for the function + * @param params.queryItem the query item from which the value should be removed from + * @param params.value the value that should be removed + * @param params.queryGroupIndex the index of the query group where the value should be removed */ - - interface RemoveValueFromQueryAPIParams { - queryItem: QueryItem; - value: QueryValue; - queryGroupIndex?: number; - } - export const removeValueFromQueryAPI = ({ queryItem, value, queryGroupIndex = 0, - }: RemoveValueFromQueryAPIParams): void => { + }: { + queryItem: QueryItem; + value: QueryValue; + queryGroupIndex?: number; + }): void => { const queryObject = { ...queryItem, values: [value], }; removeValueFromQuery(queryObject, queryGroupIndex); }; - - /** - * returns the response from the backend to the library user - * @returns the response from the backend - */ - export const getResponseAPI = (): ResponseStore => { - return $responseStore; - }; - - /** - * returns the AST to the library user - * @returns the AST - */ - export const getAstAPI = (): AstTopLayer => { - return buildAstFromQuery($queryStore); - }; - - /** - * sets the response from the backend if any changes in status are detected - * @param response the response from the backend - */ - export const updateResponseStoreAPI = (response: ResponseStore): void => { - updateResponseStore(response); - }; - - /** - * returns the catalogue to the library user - * @param category the category name (e.g. "diagnosis") - * @returns array of strings containing the bottom level items' keys - */ - export const getCriteriaAPI = (category: string): string[] => { - return getCriteria(category); - }; diff --git a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte index 08c89b70..eaa81931 100644 --- a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte +++ b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte @@ -40,7 +40,7 @@ $: options = $lensOptions?.backends as BackendOptions; - let controller: AbortController; + let controller: AbortController = new AbortController(); /** * watches the backendConfig for changes to populate the uiSiteMappingsStore with a map diff --git a/packages/lib/src/stores/response.ts b/packages/lib/src/stores/response.ts index 99aa41bf..1fb13003 100644 --- a/packages/lib/src/stores/response.ts +++ b/packages/lib/src/stores/response.ts @@ -66,9 +66,9 @@ export const getSitePopulationForCode = ( code: string, ): number => { let population: number = 0; - if (!site) return population; + if (!site || !site.group) return population; - site.group.forEach((group) => { + site?.group?.forEach((group) => { if (group.code.text === code) { population += group.population[0].count; } @@ -134,7 +134,7 @@ export const getSitePopulationForStratumCode = ( stratumCode: string, stratifier: string, ): number => { - if (!site) return 0; + if (!site || !site.group) return 0; let population: number = 0; @@ -193,7 +193,7 @@ export const getSiteStratifierCodesForGroupCode = ( site: SiteData, code: string, ): string[] => { - if (!site) return [""]; + if (!site || !site.group) return [""]; const codes: string[] = []; site.group.forEach((groupItem) => { diff --git a/packages/lib/src/styles/searchbars.css b/packages/lib/src/styles/searchbars.css index 4d45a723..39b759f9 100644 --- a/packages/lib/src/styles/searchbars.css +++ b/packages/lib/src/styles/searchbars.css @@ -167,6 +167,51 @@ lens-search-bar-multiple::part(lens-searchbar-add-button) { margin-left: var(--gap-xs); } +/** +* Lens Search Bar Info Button +*/ + +lens-search-bar-multiple::part(info-button), +lens-search-bar::part(info-button){ + background-color: var(--blue); + border-color: var(--blue); + position: relative; + padding: 0; + border: 0; + top: +2px; +} + +lens-search-bar::part(info-button-icon), +lens-search-bar-multiple::part(info-button-icon) { + height: calc(var(--font-size-s) + 8px); + width: calc(var(--font-size-s) + 8px); + filter: brightness(0) invert(1); + box-sizing: content-box; + border-radius: 50%; +} + +lens-search-bar::part(info-button-icon):hover, +lens-search-bar-multiple::part(info-button-icon):hover { + cursor: pointer; +} + +lens-search-bar::part(info-button-dialogue), +lens-search-bar-multiple::part(info-button-dialogue) { + position: absolute; + border: none; + background-color: var(--white); + width: max-content; + max-width: 200px; + z-index: 100; + padding: var(--gap-s); + top: calc(var(--gap-m) + 4px); + right: -20px; + border: solid 1px var(--light-blue); + border-radius: var(--border-radius-small); + text-align: left; +} + + /** * delete buttons in searchbar and chips @@ -187,6 +232,7 @@ lens-search-bar-multiple::part(query-delete-button) { lens-search-bar::part(query-delete-button):hover, lens-search-bar-multiple::part(query-delete-button):hover { border: solid 1px var(--orange); + color: var(--orange) } lens-search-bar::part(query-delete-button-value), @@ -194,7 +240,7 @@ lens-search-bar-multiple::part(query-delete-button-value) { font-size: var(--font-size-xxs); color: var(--white); margin: 0 var(--gap-xs) 0 var(--gap-xxs); - background-color: var(--light-blue); + background-color: var(--blue); border: var(--white) 1px solid; } From 5b26467734f76d706e0235a617349a6b1c2bf7df Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 16 May 2024 14:42:28 +0200 Subject: [PATCH 13/50] feat(json schema): add options schemas --- packages/lib/src/components/Options.wc.svelte | 10 +++- .../lib/src/interfaces/options.schema.json | 50 ++++++++++++++++--- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/packages/lib/src/components/Options.wc.svelte b/packages/lib/src/components/Options.wc.svelte index 19031957..e4a20c94 100644 --- a/packages/lib/src/components/Options.wc.svelte +++ b/packages/lib/src/components/Options.wc.svelte @@ -38,7 +38,10 @@ if (validJSON.valid === true) { $lensOptions = options; } else if (typeof options === "object") { - console.error("Lens-Options: ", validJSON.errors); + console.error( + "Lens-Options are not conform with the JSON schema", + validJSON.errors, + ); } } @@ -51,7 +54,10 @@ if (validJSON.valid === true) { $catalogue = catalogueData; } else if (typeof catalogueData === "object") { - console.error("Lens-Options: ", validJSON.errors); + console.error( + "Catalogue is not conform with the JSON schema", + validJSON.errors, + ); } } diff --git a/packages/lib/src/interfaces/options.schema.json b/packages/lib/src/interfaces/options.schema.json index 13907a75..d455c696 100644 --- a/packages/lib/src/interfaces/options.schema.json +++ b/packages/lib/src/interfaces/options.schema.json @@ -12,15 +12,23 @@ "pattern": "^.+$", "description": "The icon to use for the info button" }, - "addUrl": { + "deleteUrl": { "type": "string", "pattern": "^.+$", - "description": "The icon to use for the add button in the catalogue" + "description": "The icon to use for the info button" }, - "toggleUrl": { - "type": "string", - "pattern": "^.+$", - "description": "The icon to use for the toggle button in the catalogue" + "selectAll": { + "type": "object", + "properties": { + "text": { + "type": "string", + "pattern": "^.+$", + "description": "The text to display for the select all button" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] } }, "additionalProperties": false, @@ -61,6 +69,31 @@ "pattern": "^.+$" } }, + "accumulatedValues": { + "type": "array", + "description": "aggregate values of other data keys to include in the chart", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^.+$", + "description": "The name to be displayed in the chart" + }, + "values": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$", + "description": "The data key to be aggregated" + } + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + } + }, "tooltips": { "type": "object", "patternProperties": { @@ -133,6 +166,11 @@ "title" ] } + }, + "claimedText": { + "type": "string", + "pattern": "^.+$", + "description": "The text to be displayed when query is being executed" } }, "additionalProperties": false, From e07f20f4f1dc494535ca55f2aa9f67a190d53a78 Mon Sep 17 00:00:00 2001 From: Patrick Skowronek Date: Wed, 22 May 2024 13:09:08 +0200 Subject: [PATCH 14/50] fix: remove c% and d% from catalog, which will not be resolved --- .../public/catalogues/catalogue-dktk.json | 144 ------------------ 1 file changed, 144 deletions(-) diff --git a/packages/demo/public/catalogues/catalogue-dktk.json b/packages/demo/public/catalogues/catalogue-dktk.json index fcf31fb5..a0f0e9b4 100644 --- a/packages/demo/public/catalogues/catalogue-dktk.json +++ b/packages/demo/public/catalogues/catalogue-dktk.json @@ -2519,12 +2519,6 @@ "name": "Neuroendokriner Tumor (NET) - Grad I", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6151,12 +6145,6 @@ "name": "Adipozytäres Weichteilsarkom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6182,12 +6170,6 @@ "name": "Adipoz. Weichteilsarkom - unsicheren Verhaltens", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "D%" - } - ], [ { "value": "59847-4", @@ -6201,12 +6183,6 @@ "name": "Fibroblastisches Weichteilsarkom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6244,12 +6220,6 @@ "name": "Fibrobl. Weichteilsarkom - unsicheren Verhaltens", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "D%" - } - ], [ { "value": "59847-4", @@ -6295,12 +6265,6 @@ "name": "Fibrohistiozytäres Weichteilsarkom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6314,12 +6278,6 @@ "name": "Fibrohistioz. Weichteilsarkom - unsicheren Verhaltens", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "D%" - } - ], [ { "value": "59847-4", @@ -6341,12 +6299,6 @@ "name": "Glattmuskuläres Weichteilsarkom/Leiomyosarkom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6360,12 +6312,6 @@ "name": "Perizytisches Weichteilsarkom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6379,12 +6325,6 @@ "name": "Perizyt. Weichteilsarkom - unsicheren Verhaltens", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "D%" - } - ], [ { "value": "59847-4", @@ -6402,12 +6342,6 @@ "name": "Skelettmuskuläres Weichteilsarkom/Rhabdomyosarkom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6441,12 +6375,6 @@ "name": "Synovialsarkom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6468,12 +6396,6 @@ "name": "Vaskuläres Weichteilsarkom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6534,12 +6456,6 @@ "name": "Vask. Weichteilsarkom - unsicheren Verhaltens", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "D%" - } - ], [ { "value": "59847-4", @@ -6561,12 +6477,6 @@ "name": "Weichteilsark. d. Nervenscheiden - unsicheren Verhaltens", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "D%" - } - ], [ { "value": "59847-4", @@ -6580,12 +6490,6 @@ "name": "Sonstiges Weichteilsarkom (unklare Differenzierung)", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -6647,12 +6551,6 @@ "name": "Sonstiges Weichteilsark. (unklare Diff.) - unsicheren Verhaltens", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "D%" - } - ], [ { "value": "59847-4", @@ -6687,12 +6585,6 @@ "name": "Undifferenzierter/unklassifizierter Tumor", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -7078,12 +6970,6 @@ "name": "Chorion-Ca", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -7097,12 +6983,6 @@ "name": "Germinom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -7136,12 +7016,6 @@ "name": "Embryonal-Ca", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -7159,12 +7033,6 @@ "name": "Dottersack-Ca", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -7178,12 +7046,6 @@ "name": "Teratom", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", @@ -7217,12 +7079,6 @@ "name": "Weiterer Keimzelltumor", "description": "", "aggregatedValue": [ - [ - { - "value": "diagnosis", - "name": "C%" - } - ], [ { "value": "59847-4", From 1a2a5b1885132e8c07499f2c093965d6ac216b72 Mon Sep 17 00:00:00 2001 From: Patrick Skowronek Date: Thu, 23 May 2024 10:34:11 +0200 Subject: [PATCH 15/50] fix: order of Neuroendokrine Tumore --- .../public/catalogues/catalogue-dktk.json | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/demo/public/catalogues/catalogue-dktk.json b/packages/demo/public/catalogues/catalogue-dktk.json index a0f0e9b4..a9b6da47 100644 --- a/packages/demo/public/catalogues/catalogue-dktk.json +++ b/packages/demo/public/catalogues/catalogue-dktk.json @@ -2480,6 +2480,19 @@ "fieldType": "single-select", "type": "EQUALS", "criteria": [ + { + "key": "urn:dktk:code:65:2", + "name": "Neuroendokriner Tumor (NET) - Grad I", + "description": "", + "aggregatedValue": [ + [ + { + "value": "59847-4", + "name": "8240/3" + } + ] + ] + }, { "key": "urn:dktk:code:66:2", "name": "Neuroendokriner Tumor (NET) - Grad II", @@ -2513,19 +2526,6 @@ } ] ] - }, - { - "key": "urn:dktk:code:65:2", - "name": "Neuroendokriner Tumor (NET) - Grad I", - "description": "", - "aggregatedValue": [ - [ - { - "value": "59847-4", - "name": "8240/3" - } - ] - ] } ] }, From d600cda694c551760cf5d0771b2f2ef8b5bec12d Mon Sep 17 00:00:00 2001 From: Patrick Skowronek Date: Thu, 23 May 2024 10:37:40 +0200 Subject: [PATCH 16/50] feat: added pre check to build --- Dockerfile | 2 ++ build.sh | 2 ++ options_tester.cjs | 21 +++++++++++++++++++++ options_tester.ts | 21 +++++++++++++++++++++ package.json | 4 ++-- 5 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 build.sh create mode 100644 options_tester.cjs create mode 100644 options_tester.ts diff --git a/Dockerfile b/Dockerfile index 6e2c7c75..f624cca6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,8 @@ COPY package.json package-lock.json ./ RUN npm install COPY ./vite.config.ts ./vite.demo.config.ts ./extensions.json ./tsconfig.json ./tsconfig.node.json ./ COPY ./packages ./packages +COPY ./options_tester.cjs ./options_tester.cjs + RUN VITE_TARGET_ENVIRONMENT=${TARGET_ENVIRONMENT} npm run build RUN VITE_TARGET_ENVIRONMENT=${TARGET_ENVIRONMENT} npm run build:demo diff --git a/build.sh b/build.sh new file mode 100644 index 00000000..5b10f837 --- /dev/null +++ b/build.sh @@ -0,0 +1,2 @@ +tsc --resolveJsonModule --esModuleInterop options_tester.ts +mv options_tester.js options_tester.cjs \ No newline at end of file diff --git a/options_tester.cjs b/options_tester.cjs new file mode 100644 index 00000000..9fdfc808 --- /dev/null +++ b/options_tester.cjs @@ -0,0 +1,21 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +var options_schema_json_1 = __importDefault(require("./packages/lib/src/interfaces/options.schema.json")); +var schemasafe_1 = require("@exodus/schemasafe"); +var options_json_1 = __importDefault(require("./packages/demo/public/options.json")); +console.log("Checking Lens options"); +var parse = (0, schemasafe_1.parser)(options_schema_json_1.default, { + includeErrors: true, + allErrors: true, +}); +var validJSON = parse(JSON.stringify(options_json_1.default)); +if (validJSON.valid === true) { + console.log("Options are valid"); +} +else if (typeof options_json_1.default === "object") { + console.error("Lens-Options are not conform with the JSON schema", validJSON.errors); + process.exit(1); +} diff --git a/options_tester.ts b/options_tester.ts new file mode 100644 index 00000000..296c6d26 --- /dev/null +++ b/options_tester.ts @@ -0,0 +1,21 @@ +import optionsSchema from "./packages/lib/src/interfaces/options.schema.json"; +import { parser } from "@exodus/schemasafe"; + +import options from "./packages/demo/public/options.json"; + +console.log("Checking Lens options"); + +const parse = parser(optionsSchema, { + includeErrors: true, + allErrors: true, +}); +const validJSON = parse(JSON.stringify(options)); +if (validJSON.valid === true) { + console.log("Options are valid"); +} else if (typeof options === "object") { + console.error( + "Lens-Options are not conform with the JSON schema", + validJSON.errors, + ); + process.exit(1); +} diff --git a/package.json b/package.json index 3740beb1..b7762a64 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,8 @@ "license": "MIT", "scripts": { "start": "npm run dev -s", - "dev": "vite --config vite.demo.config.ts", - "build": "vite build", + "dev": "node options_tester.cjs && vite --config vite.demo.config.ts", + "build": "node options_tester.cjs && vite build", "build:demo": "vite build --config vite.demo.config.ts", "preview": "vite preview --config vite.demo.config.ts", "check": "svelte-check --tsconfig ./tsconfig.json", From 71c0acc2f0d71f60be8e4b9022e11eb190598854 Mon Sep 17 00:00:00 2001 From: Patrick Skowronek Date: Tue, 28 May 2024 10:39:33 +0200 Subject: [PATCH 17/50] fix: open links in a new tab --- packages/demo/src/AppCCP.svelte | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/demo/src/AppCCP.svelte b/packages/demo/src/AppCCP.svelte index aeadcefc..77a166cc 100644 --- a/packages/demo/src/AppCCP.svelte +++ b/packages/demo/src/AppCCP.svelte @@ -279,6 +279,7 @@ Clinical Communication Platform (CCP) @@ -286,14 +287,19 @@ NutzungsvereinbarungNutzungsvereinbarung DatenschutzDatenschutz - ImpressumImpressum From 31e89a8b5e817488b565f2a1a62d9530b5dafe45 Mon Sep 17 00:00:00 2001 From: Patrick Skowronek Date: Wed, 29 May 2024 10:29:19 +0200 Subject: [PATCH 18/50] fix: package.json scripts --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b7762a64..1151e188 100644 --- a/package.json +++ b/package.json @@ -28,11 +28,11 @@ "license": "MIT", "scripts": { "start": "npm run dev -s", - "dev": "node options_tester.cjs && vite --config vite.demo.config.ts", - "build": "node options_tester.cjs && vite build", - "build:demo": "vite build --config vite.demo.config.ts", + "dev": "vite --config vite.demo.config.ts", + "build": "vite build", + "build:demo": "node options_tester.cjs && vite build --config vite.demo.config.ts", "preview": "vite preview --config vite.demo.config.ts", - "check": "svelte-check --tsconfig ./tsconfig.json", + "check": "node options_tester.cjs && svelte-check --tsconfig ./tsconfig.json", "lint": "lint-staged", "watch": "rimraf dist && vite build --watch", "link": "wait-on dist/types.d.ts && cd dist/ && npm link", From a1d1680cd6e625b6f2ed0f5ac5402c8feb86adbd Mon Sep 17 00:00:00 2001 From: Patrick Skowronek Date: Wed, 5 Jun 2024 11:05:06 +0200 Subject: [PATCH 19/50] fix: changed link to user agreement --- packages/demo/src/AppCCP.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/demo/src/AppCCP.svelte b/packages/demo/src/AppCCP.svelte index 77a166cc..fbca2f1e 100644 --- a/packages/demo/src/AppCCP.svelte +++ b/packages/demo/src/AppCCP.svelte @@ -286,7 +286,7 @@ Nutzungsvereinbarung From 2172423d7386171c915cd6d8dda1cafefbe4dfa1 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 6 Jun 2024 09:08:05 +0200 Subject: [PATCH 20/50] feat(backend): add support for backend calls from project - with spot as example --- packages/demo/src/AppFragmentDevelopment.svelte | 10 +++++++++- packages/lib/src/components/Options.wc.svelte | 1 - .../buttons/SearchButtonComponenet.wc.svelte | 8 ++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/demo/src/AppFragmentDevelopment.svelte b/packages/demo/src/AppFragmentDevelopment.svelte index f02dd924..b7fd7275 100644 --- a/packages/demo/src/AppFragmentDevelopment.svelte +++ b/packages/demo/src/AppFragmentDevelopment.svelte @@ -3,6 +3,7 @@ import type { CatalogueText, MeasureItem, + Measure, QueryEvent, QueryItem, QueryValue, @@ -50,6 +51,7 @@ ], }, ]; + console.log("dktkDiagnosisMeasure", dktkDiagnosisMeasure); /** * move to config file @@ -98,7 +100,7 @@ const event = e as QueryEvent; const { ast, updateResponse, abortController } = event.detail; - const measureItems = [ + const measureItems: MeasureItem[] = [ dktkPatientsMeasure, dktkDiagnosisMeasure, dktkSpecimenMeasure, @@ -107,8 +109,13 @@ dktkHistologyMeasure, ] as MeasureItem[]; + const measures: Measure[] = measureItems.map( + (measureItem: MeasureItem) => measureItem.measure, + ); + const criteria = dataPasser.getCriteriaAPI("diagnosis"); + console.log("app cql translation", measureItems); const cql = translateAstToCql( ast, false, @@ -116,6 +123,7 @@ measureItems, criteria, ); + console.log("app build meausre", measures); const library = buildLibrary(`${cql}`); const measure = buildMeasure(library.url, measures); diff --git a/packages/lib/src/components/Options.wc.svelte b/packages/lib/src/components/Options.wc.svelte index 9a6d2885..d0c44c59 100644 --- a/packages/lib/src/components/Options.wc.svelte +++ b/packages/lib/src/components/Options.wc.svelte @@ -17,7 +17,6 @@ import { catalogue } from "../stores/catalogue"; import { measureStore } from "../stores/measures"; import { iconStore } from "../stores/icons"; - import type { Criteria } from "../types/treeData"; import type { MeasureStore } from "../types/backend"; import type { Criteria } from "../types/treeData"; import type { LensOptions } from "../types/options"; diff --git a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte index eaa81931..0f4e2575 100644 --- a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte +++ b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte @@ -57,12 +57,12 @@ }); $: catalogueKeyToResponseKeyMap.update((mappings) => { - options?.spots.forEach((spot) => { + options?.spots?.forEach((spot) => { spot.catalogueKeyToResponseKeyMap.forEach((mapping) => { mappings.set(mapping[0], mapping[1]); }); }); - options?.blazes.forEach((blaze: BlazeOption) => { + options?.blazes?.forEach((blaze: BlazeOption) => { blaze.catalogueKeyToResponseKeyMap.forEach((mapping) => { mappings.set(mapping[0], mapping[1]); }); @@ -86,7 +86,7 @@ const ast = buildAstFromQuery($queryStore); - options.spots.forEach((spot: SpotOption) => { + options?.spots?.forEach((spot: SpotOption) => { const name = spot.name; const measureItem: MeasureOption | undefined = $measureStore.find( (measureStoreItem: MeasureOption) => @@ -122,7 +122,7 @@ ); }); - options.blazes.forEach((blaze: BlazeOption) => { + options?.blazes?.forEach((blaze: BlazeOption) => { const { name, url, From 547df288a8de7217c73d4f99bb72bd32c4197378 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 6 Jun 2024 09:22:37 +0200 Subject: [PATCH 21/50] feat(backend): add config for backend urls where ast is sent --- .../buttons/SearchButtonComponenet.wc.svelte | 20 +++++++++++++++++++ packages/lib/src/types/backend.ts | 5 +++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte index 0f4e2575..88d21daf 100644 --- a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte +++ b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte @@ -156,11 +156,31 @@ backend.send(cql, controller, measures); }); + options?.customAstBackends?.forEach((customAstBackendUrl: string) => { + customBackendCallWithAst(ast, customAstBackendUrl); + }); emitEvent(ast); queryModified.set(false); }; + const customBackendCallWithAst = (ast: AstTopLayer, url: string): void => { + fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(ast), + }) + .then((response) => response.json()) + .then((data) => { + updateResponseStore(data); + }) + .catch((error) => { + console.error("Error:", error); + }); + }; + interface QueryEvent extends Event { detail: { ast: AstTopLayer; diff --git a/packages/lib/src/types/backend.ts b/packages/lib/src/types/backend.ts index d3817baa..cc439d67 100644 --- a/packages/lib/src/types/backend.ts +++ b/packages/lib/src/types/backend.ts @@ -75,8 +75,9 @@ export type BlazeOption = { }; export type BackendOptions = { - spots: SpotOption[]; - blazes: BlazeOption[]; + spots?: SpotOption[]; + blazes?: BlazeOption[]; + customAstBackends?: string[]; }; export interface QueryEvent extends Event { From 067312a37e95a271f80c56446b7ea6fbdc87a0b2 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 6 Jun 2024 09:24:41 +0200 Subject: [PATCH 22/50] feat(documentation): add documentation --- .../src/components/buttons/SearchButtonComponenet.wc.svelte | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte index 88d21daf..c8b85d83 100644 --- a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte +++ b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte @@ -164,6 +164,11 @@ queryModified.set(false); }; + /** + * Sends the ast to a custom backend + * @param ast the ast to be sent to the backend + * @param url the url of the backend + */ const customBackendCallWithAst = (ast: AstTopLayer, url: string): void => { fetch(url, { method: "POST", From 8e9789f63f309467548256497b97ce376c262da6 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 6 Jun 2024 10:33:03 +0200 Subject: [PATCH 23/50] feat(config): add backend to ui site mapping option --- packages/demo/public/options.json | 21 +++++++++++++++++-- packages/lib/src/components/Options.wc.svelte | 21 +++++++++++++++++++ .../buttons/SearchButtonComponenet.wc.svelte | 19 +---------------- .../results/ResultTableComponent.wc.svelte | 5 +---- 4 files changed, 42 insertions(+), 24 deletions(-) diff --git a/packages/demo/public/options.json b/packages/demo/public/options.json index 211f37b8..e54f859a 100644 --- a/packages/demo/public/options.json +++ b/packages/demo/public/options.json @@ -5,6 +5,24 @@ "text": "Add all" } }, + "siteMappings": { + "berlin": "Berlin", + "berlin-test": "Berlin", + "bonn": "Bonn", + "dresden": "Dresden", + "essen": "Essen", + "frankfurt": "Frankfurt", + "freiburg": "Freiburg", + "hannover": "Hannover", + "mainz": "Mainz", + "muenchen-lmu": "München(LMU)", + "muenchen-tum": "München(TUM)", + "ulm": "Ulm", + "wuerzburg": "Würzburg", + "mannheim": "Mannheim", + "dktk-test": "DKTK-Test", + "hamburg": "Hamburg" + }, "chartOptions": { "patients": { "legendMapping": { @@ -171,8 +189,7 @@ { "stratifierCode": "Histologies", "stratumCode": "1" - }, - {} + } ] } ], diff --git a/packages/lib/src/components/Options.wc.svelte b/packages/lib/src/components/Options.wc.svelte index d0c44c59..1a693870 100644 --- a/packages/lib/src/components/Options.wc.svelte +++ b/packages/lib/src/components/Options.wc.svelte @@ -20,11 +20,16 @@ import type { MeasureStore } from "../types/backend"; import type { Criteria } from "../types/treeData"; import type { LensOptions } from "../types/options"; + import { uiSiteMappingsStore } from "../stores/mappings"; export let options: LensOptions = {}; export let catalogueData: Criteria[] = []; export let measures: MeasureStore = {} as MeasureStore; + /** + * updates the icon store with the options passed in + * @param options the Lens options + */ const updateIconStore = (options: LensOptions): void => { iconStore.update((store) => { if (typeof options === "object" && "iconOptions" in options) { @@ -61,6 +66,22 @@ }); }; + /** + * watches the backendConfig for changes to populate the uiSiteMappingsStore with a map + * web components' props are json, meaning that Maps are not supported + * therefore it's a 2d array of strings which is converted to a map + */ + $: uiSiteMappingsStore.update((mappings) => { + if (!options?.siteMappings) return mappings; + console.log("options", options); + console.log(Object.entries(options?.siteMappings)); + Object.entries(options?.siteMappings)?.forEach((site) => { + mappings.set(site[0], site[1]); + }); + + return mappings; + }); + $: $lensOptions = options; $: updateIconStore(options); $: $catalogue = catalogueData; diff --git a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte index c8b85d83..c84ddf1a 100644 --- a/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte +++ b/packages/lib/src/components/buttons/SearchButtonComponenet.wc.svelte @@ -17,10 +17,7 @@ import { buildLibrary, buildMeasure } from "../../helpers/cql-measure"; import { Spot } from "../../classes/spot"; import { Blaze } from "../../classes/blaze"; - import { - catalogueKeyToResponseKeyMap, - uiSiteMappingsStore, - } from "../../stores/mappings"; + import { catalogueKeyToResponseKeyMap } from "../../stores/mappings"; import { responseStore, updateResponseStore } from "../../stores/response"; import { lensOptions } from "../../stores/options"; import type { @@ -42,20 +39,6 @@ let controller: AbortController = new AbortController(); - /** - * watches the backendConfig for changes to populate the uiSiteMappingsStore with a map - * web components' props are json, meaning that Maps are not supported - * therefore it's a 2d array of strings which is converted to a map - */ - $: uiSiteMappingsStore.update((mappings) => { - options?.spots.forEach((spot) => { - spot.uiSiteMap.forEach((site) => { - mappings.set(site[0], site[1]); - }); - }); - return mappings; - }); - $: catalogueKeyToResponseKeyMap.update((mappings) => { options?.spots?.forEach((spot) => { spot.catalogueKeyToResponseKeyMap.forEach((mapping) => { diff --git a/packages/lib/src/components/results/ResultTableComponent.wc.svelte b/packages/lib/src/components/results/ResultTableComponent.wc.svelte index e51f5f24..c32a4547 100644 --- a/packages/lib/src/components/results/ResultTableComponent.wc.svelte +++ b/packages/lib/src/components/results/ResultTableComponent.wc.svelte @@ -24,10 +24,7 @@ let claimedText: string; $: claimedText = - (($lensOptions?.tableOptions && - $lensOptions.tableOptions?.claimedText && - $lensOptions.tableOptions.claimedText) as string) || - "Processing..."; + ($lensOptions?.tableOptions?.claimedText as string) || "Processing..."; /** * data-types for the table From 03d9af01f4d50daf7624056322b868cfd70373f1 Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Thu, 6 Jun 2024 11:00:47 +0200 Subject: [PATCH 24/50] feat(options): add site mapping update schema --- packages/demo/public/options.json | 75 +----------- .../demo/src/AppFragmentDevelopment.svelte | 1 - packages/lib/src/components/Options.wc.svelte | 2 - packages/lib/src/types/options.schema.json | 110 ++++++++++++++++++ 4 files changed, 113 insertions(+), 75 deletions(-) diff --git a/packages/demo/public/options.json b/packages/demo/public/options.json index c5d73e45..e781695f 100644 --- a/packages/demo/public/options.json +++ b/packages/demo/public/options.json @@ -211,6 +211,9 @@ ] }, "backends": { + "customBackends": [ + "someUrl" + ], "spots": [ { "name": "DKTK", @@ -232,72 +235,6 @@ "dktk-test", "hamburg" ], - "uiSiteMap": [ - [ - "berlin", - "Berlin" - ], - [ - "berlin-test", - "Berlin Test" - ], - [ - "bonn", - "Bonn" - ], - [ - "dresden", - "Dresden" - ], - [ - "essen", - "Essen" - ], - [ - "frankfurt", - "Frankfurt" - ], - [ - "freiburg", - "Freiburg" - ], - [ - "hannover", - "Hannover" - ], - [ - "mainz", - "Mainz" - ], - [ - "muenchen-lmu", - "München(LMU)" - ], - [ - "muenchen-tum", - "München(TUM)" - ], - [ - "ulm", - "Ulm" - ], - [ - "wuerzburg", - "Würzburg" - ], - [ - "mannheim", - "Mannheim" - ], - [ - "dktk-test", - "DKTK-Test" - ], - [ - "hamburg", - "Hamburg" - ] - ], "catalogueKeyToResponseKeyMap": [ [ "gender", @@ -335,12 +272,6 @@ "name": "DKTK", "backendMeasures": "DKTK_STRAT_DEF_IN_INITIAL_POPULATION", "url": "http://localhost:8080", - "uiSiteMap": [ - [ - "blaze1", - "Blaze 1" - ] - ], "catalogueKeyToResponseKeyMap": [ [ "gender", diff --git a/packages/demo/src/AppFragmentDevelopment.svelte b/packages/demo/src/AppFragmentDevelopment.svelte index b7fd7275..cd0501f9 100644 --- a/packages/demo/src/AppFragmentDevelopment.svelte +++ b/packages/demo/src/AppFragmentDevelopment.svelte @@ -51,7 +51,6 @@ ], }, ]; - console.log("dktkDiagnosisMeasure", dktkDiagnosisMeasure); /** * move to config file diff --git a/packages/lib/src/components/Options.wc.svelte b/packages/lib/src/components/Options.wc.svelte index f6dc0b21..600ac086 100644 --- a/packages/lib/src/components/Options.wc.svelte +++ b/packages/lib/src/components/Options.wc.svelte @@ -116,8 +116,6 @@ */ $: uiSiteMappingsStore.update((mappings) => { if (!options?.siteMappings) return mappings; - console.log("options", options); - console.log(Object.entries(options?.siteMappings)); Object.entries(options?.siteMappings)?.forEach((site) => { mappings.set(site[0], site[1]); }); diff --git a/packages/lib/src/types/options.schema.json b/packages/lib/src/types/options.schema.json index d455c696..42bdfc2f 100644 --- a/packages/lib/src/types/options.schema.json +++ b/packages/lib/src/types/options.schema.json @@ -35,6 +35,18 @@ "unevaluatedProperties": false, "required": [] }, + "siteMappings":{ + "type": "object", + "patternProperties": { + "^.+$": { + "type": "string", + "pattern": "^.+$" + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] + }, "chartOptions": { "type": "object", "patternProperties": { @@ -235,6 +247,104 @@ "additionalProperties": false, "unevaluatedProperties": false, "required": [] + }, + "backends": { + "type": "object", + "properties": { + "customBackends": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$", + "description": "The URL of the custom backend" + } + }, + "spots": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^.+$", + "description": "The name of the spot" + }, + "backendMeasures": { + "type": "string", + "pattern": "^.+$", + "description": "The measures of the spot" + }, + "url": { + "type": "string", + "pattern": "^.+$", + "description": "The URL of the spot" + }, + "sites": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$", + "description": "The sites of the spot" + } + }, + "catalogueKeyToResponseKeyMap": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$", + "description": "The mapping of the catalogue key to the response key" + } + } + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": ["name", "backendMeasures", "url", "sites"] + } + }, + "blazes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": "^.+$", + "description": "The name of the blaze" + }, + "backendMeasures": { + "type": "string", + "pattern": "^.+$", + "description": "The measures of the blaze" + }, + "url": { + "type": "string", + "pattern": "^.+$", + "description": "The URL of the blaze" + }, + "catalogueKeyToResponseKeyMap": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string", + "pattern": "^.+$", + "description": "The mapping of the catalogue key to the response key" + } + } + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": ["name", "backendMeasures", "url"] + } + } + }, + "additionalProperties": false, + "unevaluatedProperties": false, + "required": [] } }, "additionalProperties": false, From 15f30ca906965bdcbf48010cd1925dbecb983e0b Mon Sep 17 00:00:00 2001 From: Mats Johansen Date: Wed, 12 Jun 2024 10:05:07 +0200 Subject: [PATCH 25/50] refactor(interfaces): refactor interfaces add different options for different environments --- packages/demo/public/options-ccp-demo.json | 305 ++++++++++++++++++ packages/demo/public/options-ccp-prod.json | 268 +++++++++++++++ .../public/{options.json => options-dev.json} | 0 packages/demo/src/AppCCP.svelte | 96 ++---- .../demo/src/AppFragmentDevelopment.svelte | 6 +- .../lib/src/components/DataPasser.wc.svelte | 26 +- packages/lib/src/types/DataPasser.d.ts | 10 - packages/lib/src/types/backend.ts | 7 +- packages/lib/src/types/dataPasser.ts | 32 ++ 9 files changed, 644 insertions(+), 106 deletions(-) create mode 100644 packages/demo/public/options-ccp-demo.json create mode 100644 packages/demo/public/options-ccp-prod.json rename packages/demo/public/{options.json => options-dev.json} (100%) delete mode 100644 packages/lib/src/types/DataPasser.d.ts create mode 100644 packages/lib/src/types/dataPasser.ts diff --git a/packages/demo/public/options-ccp-demo.json b/packages/demo/public/options-ccp-demo.json new file mode 100644 index 00000000..882fcb1c --- /dev/null +++ b/packages/demo/public/options-ccp-demo.json @@ -0,0 +1,305 @@ +{ + "iconOptions": { + "deleteUrl": "delete_icon.svg", + "infoUrl": "info-circle-svgrepo-com.svg", + "selectAll": { + "text": "Add all" + } + }, + "siteMappings": { + "berlin": "Berlin", + "berlin-test": "Berlin", + "bonn": "Bonn", + "dresden": "Dresden", + "essen": "Essen", + "frankfurt": "Frankfurt", + "freiburg": "Freiburg", + "hannover": "Hannover", + "mainz": "Mainz", + "muenchen-lmu": "München(LMU)", + "muenchen-tum": "München(TUM)", + "ulm": "Ulm", + "wuerzburg": "Würzburg", + "mannheim": "Mannheim", + "dktk-test": "DKTK-Test", + "hamburg": "Hamburg" + }, + "chartOptions": { + "patients": { + "legendMapping": { + "berlin": "Berlin", + "berlin-test": "Berlin", + "bonn": "Bonn", + "dresden": "Dresden", + "essen": "Essen", + "frankfurt": "Frankfurt", + "freiburg": "Freiburg", + "hannover": "Hannover", + "mainz": "Mainz", + "muenchen-lmu": "München(LMU)", + "muenchen-tum": "München(TUM)", + "ulm": "Ulm", + "wuerzburg": "Würzburg", + "mannheim": "Mannheim", + "dktk-test": "DKTK-Test", + "hamburg": "Hamburg" + } + }, + "gender": { + "legendMapping": { + "male": "Männlich", + "female": "Weiblich", + "unknown": "Unbekannt", + "other": "Divers" + } + }, + "diagnosis": { + "hintText": [ + "Bei Patienten mit mehreren onkologischen Diagnosen werden auch Einträge angezeigt, die ggfs. nicht den ausgewählten Suchkriterien entsprechen." + ] + }, + "age_at_diagnosis": { + "hintText": [ + "Bei Patienten mit mehreren Erstdiagnosen werden auch Einträge angezeigt, die ggfs. außerhalb der Suchkriterien liegen. " + ] + }, + "75186-7": { + "hintText": [ + "\"verstorben\": ein Todesdatum ist dokumentiert oder das aktuelle Lebensalter ist größer 123 Jahre.", + "\"lebend\": wird angenommen, wenn kein Todesdatum dokumentiert ist oder das aktuelle Lebensalter nicht 123 Jahre überschritten hat.", + "\"unbekannt\": kein Geburtsdatum oder Todesdatum bekannt." + ] + }, + "therapy_of_tumor": { + "aggregations": [ + "medicationStatements" + ], + "tooltips": { + "OP": "Operationen", + "ST": "Strahlentherapien", + "medicationStatements": "Systemische Therapien" + } + }, + "medicationStatements": { + "hintText": [ + "Art der systemischen oder abwartenden Therapie (ADT Basisdatensatz Versionen 2014, 2021)" + ], + "tooltips": { + "CH": "Chemotherapie", + "HO": "Hormontherapie", + "IM": "Immun-/Antikörpertherapie", + "KM": "Knochenmarktransplantation", + "ZS": "zielgerichtete Substanzen", + "CI": "Chemo- + Immun-/Antikörpertherapie", + "CZ": "Chemotherapie + zielgerichtete Substanzen", + "CIZ": "Chemo- + Immun-/Antikörpertherapie + zielgerichtete Substanzen", + "IZ": "Immun-/Antikörpertherapie + zielgerichtete Substanzen", + "SZ": "Stammzelltransplantation (inklusive Knochenmarktransplantation)", + "AS": "Active Surveillance", + "WS": "Wait and see", + "WW": "Watchful Waiting", + "SO": "Sonstiges" + } + }, + "sample_kind": { + "hintText": [ + "Verteilung der Probentypen die mit den identifizierten Patienten verbunden sind." + ], + "accumulatedValues": [ + { + "name": "ffpe-tissue", + "values": [ + "tissue-ffpe", + "tumor-tissue-ffpe", + "normal-tissue-ffpe", + "other-tissue-ffpe" + ] + }, + { + "name": "frozen-tissue", + "values": [ + "tissue-frozen", + "tumor-tissue-frozen", + "normal-tissue-frozen", + "other-tissue-frozen" + ] + } + ], + "tooltips": { + "ffpe-tissue": "Gewebe FFPE", + "frozen-tissue": "Gewebe schockgefroren", + "tissue-other": "Gewebe, Andere Konservierungsart", + "whole-blood": "Vollblut", + "blood-serum": "Serum", + "blood-plasma": "Plasma", + "buffy-coat": "Buffy Coat", + "peripheral-blood-cells": "Periphere mononukleäre Blutzellen (PBMC)", + "dried-whole-blood": "Blutkarten", + "swab": "Abstrich", + "ascites": "Aszites", + "stool-faeces": "Stuhl", + "urine": "Urin", + "csf-liquor": "Liquor", + "bone-marrow": "Knochenmark", + "saliva": "Speichel", + "liquid-other": "Flüssigprobe, Andere", + "dna": "DNA", + "rna": "RNA", + "derivative-other": "Derivat, Andere" + }, + "legendMapping":{ + "ffpe-tissue": "Gewebe FFPE", + "frozen-tissue": "Gewebe schockgefroren", + "tissue-other": "Gewebe, Andere Konservierungsart", + "whole-blood": "Vollblut", + "blood-serum": "Serum", + "blood-plasma": "Plasma", + "buffy-coat": "Buffy Coat", + "peripheral-blood-cells": "Periphere mononukleäre Blutzellen (PBMC)", + "dried-whole-blood": "Blutkarten", + "swab": "Abstrich", + "ascites": "Aszites", + "stool-faeces": "Stuhl", + "urine": "Urin", + "csf-liquor": "Liquor", + "bone-marrow": "Knochenmark", + "saliva": "Speichel", + "liquid-other": "Flüssigprobe, Andere", + "dna": "DNA", + "rna": "RNA", + "derivative-other": "Derivat, Andere" + } + } + }, + "tableOptions": { + "headerData": [ + { + "title": "Standorte", + "dataKey": "site" + }, + { + "title": "Patienten", + "dataKey": "patients" + }, + { + "title": "Bioproben*", + "aggregatedDataKeys": [ + { + "groupCode": "specimen" + }, + { + "stratifierCode": "Histologies", + "stratumCode": "1" + } + ] + } + ], + "claimedText": "Processing..." + }, + "resultSummaryOptions": { + "title": "Ergebnisse", + "infoButtonText": "Um eine Re-Identifizierung zu erschweren, werden Standortergebnisse modifiziert und auf Zehnerstellen gerundet. Meldet ein Standort keinen Treffer, wird für diesen null angezeigt.", + "dataTypes": [ + { + "title": "Standorte", + "dataKey": "collections" + }, + { + "title": "Patienten", + "dataKey": "patients" + } + ] + }, + "backends": { + "spots": [ + { + "name": "DKTK", + "backendMeasures": "DKTK_STRAT_DEF_IN_INITIAL_POPULATION", + "url": "https://backend.demo.lens.samply.de/prod/", + "sites": [ + "berlin", + "dresden", + "essen", + "frankfurt", + "freiburg", + "hannover", + "mainz", + "muenchen-lmu", + "muenchen-tum", + "ulm", + "wuerzburg", + "mannheim", + "dktk-test", + "hamburg" + ], + "catalogueKeyToResponseKeyMap": [ + [ + "gender", + "Gender" + ], + [ + "age_at_diagnosis", + "Age" + ], + [ + "diagnosis", + "diagnosis" + ], + [ + "medicationStatements", + "MedicationType" + ], + [ + "sample_kind", + "sample_kind" + ], + [ + "therapy_of_tumor", + "ProcedureType" + ], + [ + "75186-7", + "75186-7" + ] + ] + } + ], + "blazes": [ + { + "name": "DKTK", + "backendMeasures": "DKTK_STRAT_DEF_IN_INITIAL_POPULATION", + "url": "http://localhost:8080", + "catalogueKeyToResponseKeyMap": [ + [ + "gender", + "Gender" + ], + [ + "age_at_diagnosis", + "Age" + ], + [ + "diagnosis", + "diagnosis" + ], + [ + "medicationStatements", + "MedicationType" + ], + [ + "sample_kind", + "sample_kind" + ], + [ + "therapy_of_tumor", + "ProcedureType" + ], + [ + "75186-7", + "75186-7" + ] + ] + } + ] + } +} diff --git a/packages/demo/public/options-ccp-prod.json b/packages/demo/public/options-ccp-prod.json new file mode 100644 index 00000000..d783ae1b --- /dev/null +++ b/packages/demo/public/options-ccp-prod.json @@ -0,0 +1,268 @@ +{ + "iconOptions": { + "deleteUrl": "delete_icon.svg", + "infoUrl": "info-circle-svgrepo-com.svg", + "selectAll": { + "text": "Add all" + } + }, + "siteMappings": { + "berlin": "Berlin", + "berlin-test": "Berlin", + "bonn": "Bonn", + "dresden": "Dresden", + "essen": "Essen", + "frankfurt": "Frankfurt", + "freiburg": "Freiburg", + "hannover": "Hannover", + "mainz": "Mainz", + "muenchen-lmu": "München(LMU)", + "muenchen-tum": "München(TUM)", + "ulm": "Ulm", + "wuerzburg": "Würzburg", + "mannheim": "Mannheim", + "dktk-test": "DKTK-Test", + "hamburg": "Hamburg" + }, + "chartOptions": { + "patients": { + "legendMapping": { + "berlin": "Berlin", + "berlin-test": "Berlin", + "bonn": "Bonn", + "dresden": "Dresden", + "essen": "Essen", + "frankfurt": "Frankfurt", + "freiburg": "Freiburg", + "hannover": "Hannover", + "mainz": "Mainz", + "muenchen-lmu": "München(LMU)", + "muenchen-tum": "München(TUM)", + "ulm": "Ulm", + "wuerzburg": "Würzburg", + "mannheim": "Mannheim", + "dktk-test": "DKTK-Test", + "hamburg": "Hamburg" + } + }, + "gender": { + "legendMapping": { + "male": "Männlich", + "female": "Weiblich", + "unknown": "Unbekannt", + "other": "Divers" + } + }, + "diagnosis": { + "hintText": [ + "Bei Patienten mit mehreren onkologischen Diagnosen werden auch Einträge angezeigt, die ggfs. nicht den ausgewählten Suchkriterien entsprechen." + ] + }, + "age_at_diagnosis": { + "hintText": [ + "Bei Patienten mit mehreren Erstdiagnosen werden auch Einträge angezeigt, die ggfs. außerhalb der Suchkriterien liegen. " + ] + }, + "75186-7": { + "hintText": [ + "\"verstorben\": ein Todesdatum ist dokumentiert oder das aktuelle Lebensalter ist größer 123 Jahre.", + "\"lebend\": wird angenommen, wenn kein Todesdatum dokumentiert ist oder das aktuelle Lebensalter nicht 123 Jahre überschritten hat.", + "\"unbekannt\": kein Geburtsdatum oder Todesdatum bekannt." + ] + }, + "therapy_of_tumor": { + "aggregations": [ + "medicationStatements" + ], + "tooltips": { + "OP": "Operationen", + "ST": "Strahlentherapien", + "medicationStatements": "Systemische Therapien" + } + }, + "medicationStatements": { + "hintText": [ + "Art der systemischen oder abwartenden Therapie (ADT Basisdatensatz Versionen 2014, 2021)" + ], + "tooltips": { + "CH": "Chemotherapie", + "HO": "Hormontherapie", + "IM": "Immun-/Antikörpertherapie", + "KM": "Knochenmarktransplantation", + "ZS": "zielgerichtete Substanzen", + "CI": "Chemo- + Immun-/Antikörpertherapie", + "CZ": "Chemotherapie + zielgerichtete Substanzen", + "CIZ": "Chemo- + Immun-/Antikörpertherapie + zielgerichtete Substanzen", + "IZ": "Immun-/Antikörpertherapie + zielgerichtete Substanzen", + "SZ": "Stammzelltransplantation (inklusive Knochenmarktransplantation)", + "AS": "Active Surveillance", + "WS": "Wait and see", + "WW": "Watchful Waiting", + "SO": "Sonstiges" + } + }, + "sample_kind": { + "hintText": [ + "Verteilung der Probentypen die mit den identifizierten Patienten verbunden sind." + ], + "accumulatedValues": [ + { + "name": "ffpe-tissue", + "values": [ + "tissue-ffpe", + "tumor-tissue-ffpe", + "normal-tissue-ffpe", + "other-tissue-ffpe" + ] + }, + { + "name": "frozen-tissue", + "values": [ + "tissue-frozen", + "tumor-tissue-frozen", + "normal-tissue-frozen", + "other-tissue-frozen" + ] + } + ], + "tooltips": { + "ffpe-tissue": "Gewebe FFPE", + "frozen-tissue": "Gewebe schockgefroren", + "tissue-other": "Gewebe, Andere Konservierungsart", + "whole-blood": "Vollblut", + "blood-serum": "Serum", + "blood-plasma": "Plasma", + "buffy-coat": "Buffy Coat", + "peripheral-blood-cells": "Periphere mononukleäre Blutzellen (PBMC)", + "dried-whole-blood": "Blutkarten", + "swab": "Abstrich", + "ascites": "Aszites", + "stool-faeces": "Stuhl", + "urine": "Urin", + "csf-liquor": "Liquor", + "bone-marrow": "Knochenmark", + "saliva": "Speichel", + "liquid-other": "Flüssigprobe, Andere", + "dna": "DNA", + "rna": "RNA", + "derivative-other": "Derivat, Andere" + }, + "legendMapping":{ + "ffpe-tissue": "Gewebe FFPE", + "frozen-tissue": "Gewebe schockgefroren", + "tissue-other": "Gewebe, Andere Konservierungsart", + "whole-blood": "Vollblut", + "blood-serum": "Serum", + "blood-plasma": "Plasma", + "buffy-coat": "Buffy Coat", + "peripheral-blood-cells": "Periphere mononukleäre Blutzellen (PBMC)", + "dried-whole-blood": "Blutkarten", + "swab": "Abstrich", + "ascites": "Aszites", + "stool-faeces": "Stuhl", + "urine": "Urin", + "csf-liquor": "Liquor", + "bone-marrow": "Knochenmark", + "saliva": "Speichel", + "liquid-other": "Flüssigprobe, Andere", + "dna": "DNA", + "rna": "RNA", + "derivative-other": "Derivat, Andere" + } + } + }, + "tableOptions": { + "headerData": [ + { + "title": "Standorte", + "dataKey": "site" + }, + { + "title": "Patienten", + "dataKey": "patients" + }, + { + "title": "Bioproben*", + "aggregatedDataKeys": [ + { + "groupCode": "specimen" + }, + { + "stratifierCode": "Histologies", + "stratumCode": "1" + } + ] + } + ], + "claimedText": "Processing..." + }, + "resultSummaryOptions": { + "title": "Ergebnisse", + "infoButtonText": "Um eine Re-Identifizierung zu erschweren, werden Standortergebnisse modifiziert und auf Zehnerstellen gerundet. Meldet ein Standort keinen Treffer, wird für diesen null angezeigt.", + "dataTypes": [ + { + "title": "Standorte", + "dataKey": "collections" + }, + { + "title": "Patienten", + "dataKey": "patients" + } + ] + }, + "backends": { + "spots": [ + { + "name": "DKTK", + "backendMeasures": "DKTK_STRAT_DEF_IN_INITIAL_POPULATION", + "url": "https://backend.data.dktk.dkfz.de/prod/", + "sites": [ + "berlin", + "dresden", + "essen", + "frankfurt", + "freiburg", + "hannover", + "mainz", + "muenchen-lmu", + "muenchen-tum", + "ulm", + "wuerzburg", + "mannheim", + "dktk-test", + "hamburg" + ], + "catalogueKeyToResponseKeyMap": [ + [ + "gender", + "Gender" + ], + [ + "age_at_diagnosis", + "Age" + ], + [ + "diagnosis", + "diagnosis" + ], + [ + "medicationStatements", + "MedicationType" + ], + [ + "sample_kind", + "sample_kind" + ], + [ + "therapy_of_tumor", + "ProcedureType" + ], + [ + "75186-7", + "75186-7" + ] + ] + } + ] + } +} diff --git a/packages/demo/public/options.json b/packages/demo/public/options-dev.json similarity index 100% rename from packages/demo/public/options.json rename to packages/demo/public/options-dev.json diff --git a/packages/demo/src/AppCCP.svelte b/packages/demo/src/AppCCP.svelte index a43aa57f..5cd13fe6 100644 --- a/packages/demo/src/AppCCP.svelte +++ b/packages/demo/src/AppCCP.svelte @@ -1,6 +1,9 @@
diff --git a/packages/demo/src/AppFragmentDevelopment.svelte b/packages/demo/src/AppFragmentDevelopment.svelte index cd0501f9..8cc6be2c 100644 --- a/packages/demo/src/AppFragmentDevelopment.svelte +++ b/packages/demo/src/AppFragmentDevelopment.svelte @@ -1,5 +1,4 @@