diff --git a/src/sites/iva/iva-app.js b/src/sites/iva/iva-app.js index b3cac438fc..bedf508cf0 100644 --- a/src/sites/iva/iva-app.js +++ b/src/sites/iva/iva-app.js @@ -1257,7 +1257,6 @@ class IvaApp extends LitElement { } - ${console.log("Enabled components", Object.keys(this.config.enabledComponents).filter(key => this.config.enabledComponents[key])) }
${this.config.enabledComponents.home ? html`
diff --git a/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-grid.js b/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-grid.js new file mode 100644 index 0000000000..7bb20c8628 --- /dev/null +++ b/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-grid.js @@ -0,0 +1,293 @@ +import {LitElement, html} from "lit"; +import UtilsNew from "../../../core/utils-new.js"; +import GridCommons from "../../commons/grid-commons.js"; +import VariantGridFormatter from "../../variant/variant-grid-formatter.js"; +import VariantInterpreterGridFormatter from "../../variant/interpretation/variant-interpreter-grid-formatter.js"; +import "../../commons/opencb-grid-toolbar.js"; +import "../../loading-spinner.js"; + +export default class PharmacogenomicsGrid extends LitElement { + + constructor() { + super(); + this.#init(); + } + + createRenderRoot() { + return this; + } + + static get properties() { + return { + opencgaSession: { + type: Object, + }, + sampleId: { + type: String, + }, + variants: { + type: Array, + }, + config: { + type: Object, + }, + }; + } + + #init() { + this._prefix = UtilsNew.randomString(8); + this._config = this.getDefaultConfig(); + } + + update(changedProperties) { + if (changedProperties.has("opencgaSession") || changedProperties.has("config")) { + this._config = { + ...this.getDefaultConfig(), + ...this.config, + }; + this.gridCommons = new GridCommons(this._prefix + "PgxTable", this, this._config); + } + + super.update(changedProperties); + } + + updated(changedProperties) { + if (changedProperties.has("variants") || changedProperties.has("config") || changedProperties.has("opencgaSession")) { + this.renderTable(); + } + } + + renderTable() { + return this.renderLocalTable(); + } + + renderLocalTable() { + this.table = $(`#${this._prefix}PgxTable`); + this.table.bootstrapTable("destroy"); + this.table.bootstrapTable({ + data: this.variants, + columns: this.getDefaultColumns(), + sidePagination: "local", + iconsPrefix: GridCommons.GRID_ICONS_PREFIX, + icons: GridCommons.GRID_ICONS, + uniqueId: "id", + pagination: this._config.pagination, + pageSize: this._config.pageSize, + pageList: this._config.pageList, + detailView: this._config.detailView, + detailFormatter: (value, row) => this.detailFormatter(value, row), + + // This has been added to make the grid properties in all bootstrap table formatters, as some grid formattes needs them + variantGrid: this, + + // onClickRow: (row, selectedElement) => this.gridCommons.onClickRow(row.id, row, selectedElement), + onDblClickRow: (row, element) => { + if (this._config.detailView) { + if (element[0].innerHTML.includes("fa-plus")) { + $("#" + this.gridId).bootstrapTable("expandRow", element[0].dataset.index); + } else { + $("#" + this.gridId).bootstrapTable("collapseRow", element[0].dataset.index); + } + } + }, + // onExpandRow: (index, row) => { + // this.gridCommons.onClickRow(row.id, row, this.querySelector(`tr[data-index="${index}"]`)); + // }, + onPostBody: data => { + this.gridCommons.onLoadSuccess({rows: data, total: data.length}, 2); + }, + }); + } + + detailFormatter(value, row) { + let result = "
"; + let detailHtml = ""; + if (row?.annotation?.pharmacogenomics) { + detailHtml += "

Drugs

"; + detailHtml += "
"; + detailHtml += this.drugsTableFormatter(row); + detailHtml += "
"; + } + result += detailHtml + "
"; + return result; + } + + drugsTableFormatter(variant) { + const drugsRows = variant.annotation.pharmacogenomics + // .filter(item => item.types.includes("Drug")) + .map(item => { + const phenotypes = item.annotations.map(annotation => `
${annotation.phenotypes.join(", ")}
`); + const phenotypeTypes = item.annotations.map(annotation => `
${annotation.phenotypeType}
`); + return ` + + ${item.id} + ${item.name || "-"} + ${item.source || "-"} + ${phenotypes.join("")} + ${phenotypeTypes.join("")} + + `; + }); + + return ` + + + + + + + + + + + + ${drugsRows.join("")} + +
IDNameSourcePhenotypesPhenotype Types
+ `; + } + + render() { + return html` +
+
+
+ `; + } + + getDefaultColumns() { + return [ + [ + { + id: "id", + title: "Variant", + field: "id", + rowspan: 2, + colspan: 1, + formatter: (value, row, index) => { + return VariantGridFormatter.variantFormatter(value, row, index, this.opencgaSession.project.organism.assembly, this._config); + }, + }, + { + id: "genotype", + title: "Genotype", + rowspan: 2, + colspan: 1, + field: { + sampleId: this.sampleId, + quality: this._config.quality, + config: this._config, + }, + formatter: VariantInterpreterGridFormatter.sampleGenotypeFormatter, + align: "center", + nucleotideGenotype: true, + visible: !!this.sampleId, + }, + { + id: "gene", + title: "Gene", + field: "gene", + rowspan: 2, + colspan: 1, + formatter: (value, row, index) => VariantGridFormatter.geneFormatter(row, index, {}, this.opencgaSession, this._config), + }, + { + id: "consequenceType", + title: "Consequence Type", + field: "consequenceType", + rowspan: 2, + colspan: 1, + formatter: (value, row) => VariantGridFormatter.consequenceTypeFormatter(value, row, null, this._config), + }, + { + id: "populationFrequencies", + title: "Reference Population Frequencies", + field: "populationFrequencies", + rowspan: 2, + colspan: 1, + formatter: VariantInterpreterGridFormatter.clinicalPopulationFrequenciesFormatter.bind(this), + }, + { + id: "clinicalInfo", + title: "Clinical Info", + rowspan: 1, + colspan: 3, + align: "center" + }, + ], + [ + { + id: "clinvar", + title: "ClinVar", + field: "clinvar", + colspan: 1, + rowspan: 1, + formatter: VariantGridFormatter.clinicalTraitAssociationFormatter, + align: "center", + }, + { + id: "cosmic", + title: "Cosmic", + field: "cosmic", + colspan: 1, + rowspan: 1, + formatter: VariantGridFormatter.clinicalTraitAssociationFormatter, + align: "center", + }, + { + id: "hotspots", + title: "Cancer
Hotspots", + field: "hotspots", + colspan: 1, + rowspan: 1, + formatter: VariantGridFormatter.clinicalCancerHotspotsFormatter, + align: "center", + }, + ], + ]; + } + + getDefaultConfig() { + return { + pagination: true, + pageSize: 10, + pageList: [5, 10, 25], + detailView: true, + quality: { + qual: 30, + dp: 20 + }, + populationFrequencies: [ + "1000G:ALL", + "GNOMAD_GENOMES:ALL", + "GNOMAD_EXOMES:ALL", + ], + populationFrequenciesConfig: { + displayMode: "FREQUENCY_BOX" + }, + genotype: { + type: "ALLELES" + }, + geneSet: { + ensembl: true, + refseq: true, + }, + consequenceType: { + // all: false, + maneTranscript: true, + gencodeBasicTranscript: false, + ensemblCanonicalTranscript: true, + refseqTranscript: true, + ccdsTranscript: false, + ensemblTslTranscript: false, + proteinCodingTranscript: false, + highImpactConsequenceTypeTranscript: false, + + showNegativeConsequenceTypes: true + }, + }; + } + +} + +customElements.define("pharmacogenomics-grid", PharmacogenomicsGrid); diff --git a/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-report.js b/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-report.js new file mode 100644 index 0000000000..ac4d421cef --- /dev/null +++ b/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-report.js @@ -0,0 +1,169 @@ +import {LitElement, html, nothing} from "lit"; +import {CellBaseClient} from "../../../core/clients/cellbase/cellbase-client.js"; +import "./pharmacogenomics-grid.js"; +import "../../commons/tool-header.js"; +import "../../loading-spinner.js"; + +export default class PharmacogenomicsReport extends LitElement { + + constructor() { + super(); + this.#init(); + } + + createRenderRoot() { + return this; + } + + static get properties() { + return { + sampleId: { + type: String, + }, + active: { + type: Boolean, + }, + opencgaSession: { + type: Object, + }, + }; + } + + #init() { + this.active = true; + this.loading = false; + this.error = false; + this.variants = []; + this.config = this.getDefaultConfig(); + } + + update(changedProperties) { + if (changedProperties.has("sampleId") || changedProperties.has("opencgaSession") || changedProperties.has("active")) { + this.sampleIdObserver(); + } + super.update(changedProperties); + } + + async sampleIdObserver() { + this.variants = []; + if (this.opencgaSession && this.sampleId && this.active) { + this.error = false; + this.loading = true; + this.requestUpdate(); + try { + // 0. Initialize Cellbase client instance + const cellbaseClient = new CellBaseClient({ + // host: this.opencgaSession?.project?.cellbase?.url || this.opencgaSession?.project?.internal?.cellbase?.url, + host: "https://ws.zettagenomics.com/cellbase", + version: "v5.5", + species: "hsapiens", + }); + + // 1. Import all PGx variants from Cellbase + const pgxVariantsResponse = await cellbaseClient.get("clinical", "pharmacogenomics", null, "distinct", { + field: "variants.location", + assembly: "grch38", + dataRelease: "5", + }); + + // 2. Get the list of variants available in OpenCGA + const pgxVariants = pgxVariantsResponse.responses[0].results; + const variants = new Map(); + for (let i = 0; i < pgxVariants.length; i = i + 200) { + const variantsPromises = []; + const variantsBatch = pgxVariants.slice(i, i + 200); + for (let j = 0; j < variantsBatch.length; j = j + 50) { + const chunk = variantsBatch.slice(j, j + 50); + const query = { + region: chunk.join(","), + sample: this.sampleId, + // include: "id", + study: this.opencgaSession.study.fqn, + includeSampleId: true, + }; + variantsPromises.push(this.opencgaSession.opencgaClient.clinical().queryVariant(query)); + } + const variantsResponses = await Promise.all(variantsPromises); + variantsResponses.forEach(variantResponse => { + variantResponse.responses[0].results.forEach(variant => { + variants.set(variant.id, variant); + }); + }); + } + + // 4. Import pharmacogenomics annotation from cellbase + const ids = Array.from(variants.keys()); + for (let i = 0; i < ids.length; i = i + 200) { + const idsBatch = ids.slice(i, i + 200); + const promises = []; + for (let j = 0; j < idsBatch.length; j = j + 50) { + const chunk = idsBatch.slice(j, j + 50); + const query = { + assembly: "grch38", + dataRelease: "5", + include: "pharmacogenomics", + }; + promises.push(cellbaseClient.get("genomic", "variant", chunk.join(","), "annotation", query)); + } + const responses = await Promise.all(promises); + responses.forEach(pgxAnnotationResponse => { + pgxAnnotationResponse.responses.forEach(response => { + if (variants.has(response.id)) { + variants.get(response.id).annotation.pharmacogenomics = response.results[0].pharmacogenomics; + } + }); + }); + } + + // 5. Save variants and request update + this.variants = Array.from(variants.values()); + } catch (error) { + console.error(error); + this.error = true; + } + this.loading = false; + this.requestUpdate(); + } + } + + render() { + if (this.loading) { + return html` +
+ +
+ `; + } + if (this.error) { + return html` +
+
+ Error getting Pharmacogenomics info. Plase try again later. +
+
+ `; + } + + return html` +
+ ${this.config.showToolTitle ? html` + + ` : nothing} + + +
+ `; + } + + getDefaultConfig() { + return { + showToolTitle: true, + }; + } + +} + +customElements.define("pharmacogenomics-report", PharmacogenomicsReport); diff --git a/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-summary.js b/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-summary.js new file mode 100644 index 0000000000..df1469be03 --- /dev/null +++ b/src/webcomponents/clinical/pharmacogenomics/pharmacogenomics-summary.js @@ -0,0 +1,49 @@ +import {LitElement, html} from "lit"; + +export default class PharmacogenomicsSummary extends LitElement { + + constructor() { + super(); + this.#init(); + } + + createRenderRoot() { + return this; + } + + static get properties() { + return { + variant: { + type: String, + }, + active: { + type: Boolean, + }, + opencgaSession: { + type: Object, + }, + }; + } + + #init() { + this.active = true; + this.config = this.getDefaultConfig(); + } + + update(changedProperties) { + super.update(changedProperties); + } + + render() { + return html` +
Pharmacogenomics Summary
+ `; + } + + getDefaultConfig() { + return {}; + } + +} + +customElements.define("pharmacogenomics-summary", PharmacogenomicsSummary); diff --git a/src/webcomponents/variant/interpretation/variant-interpreter-browser-template.js b/src/webcomponents/variant/interpretation/variant-interpreter-browser-template.js index 3c1175c46e..9109564ff8 100644 --- a/src/webcomponents/variant/interpretation/variant-interpreter-browser-template.js +++ b/src/webcomponents/variant/interpretation/variant-interpreter-browser-template.js @@ -20,6 +20,7 @@ import ClinicalAnalysisManager from "../../clinical/clinical-analysis-manager.js import LitUtils from "../../commons/utils/lit-utils.js"; import NotificationUtils from "../../commons/utils/notification-utils.js"; import OpencgaCatalogUtils from "../../../core/clients/opencga/opencga-catalog-utils.js"; +import {CellBaseClient} from "../../../core/clients/cellbase/cellbase-client.js"; import UtilsNew from "../../../core/utils-new.js"; import "./variant-interpreter-browser-toolbar.js"; import "./variant-interpreter-grid.js"; @@ -362,6 +363,55 @@ class VariantInterpreterBrowserTemplate extends LitElement { } } + async onFilterPharmacogenomicsVariants() { + // 0. Initialize Cellbase client instance + const cellbaseClient = new CellBaseClient({ + // host: this.opencgaSession?.project?.cellbase?.url || this.opencgaSession?.project?.internal?.cellbase?.url, + host: "https://ws.zettagenomics.com/cellbase", + version: "v5.5", + species: "hsapiens", + }); + + // 1. Import all PGx variants from Cellbase + const pgxVariantsResponse = await cellbaseClient.get("clinical", "pharmacogenomics", null, "distinct", { + field: "variants.location", + assembly: "grch38", + dataRelease: "5", + }); + + // 2. Get the list of variants available in OpenCGA + const pgxVariants = pgxVariantsResponse.responses[0].results; + const chunkSize = 200; + const promises = []; + for (let i = 0; i < pgxVariants.length; i = i + chunkSize) { + const chunk = pgxVariants.slice(i, i + chunkSize); + const query = { + region: chunk.join(","), + sample: this.clinicalAnalysis.proband.samples[0].id, + include: "id", + study: this.opencgaSession.study.fqn, + }; + promises.push(this.opencgaSession.opencgaClient.clinical().queryVariant(query)); + } + const variantsResponses = await Promise.all(promises); + + // 3. Generate the list of unique variants to filter + const variantIds = new Set(); + variantsResponses.forEach(variantResponse => { + variantResponse.responses[0].results.forEach(variant => { + variantIds.add(variant.id); + }); + }); + + // 4. Update the query + this.query = { + ...this.query, + id: Array.from(variantIds).join(","), + }; + this.notifyQueryChange(); + this.requestUpdate(); + } + render() { // Check Project exists if (!this.opencgaSession?.study) { @@ -459,6 +509,7 @@ class VariantInterpreterBrowserTemplate extends LitElement { .variantInclusionState="${this.variantInclusionState}" .write="${OpencgaCatalogUtils.checkPermissions(this.opencgaSession.study, this.opencgaSession.user.id, "WRITE_CLINICAL_ANALYSIS")}" @filterVariants="${this.onFilterVariants}" + @filterPharmacogenomicsVariants="${this.onFilterPharmacogenomicsVariants}" @resetVariants="${this.onResetVariants}" @saveInterpretation="${this.onSaveVariants}"> diff --git a/src/webcomponents/variant/interpretation/variant-interpreter-browser-toolbar.js b/src/webcomponents/variant/interpretation/variant-interpreter-browser-toolbar.js index aa34410a38..bbac872cd3 100644 --- a/src/webcomponents/variant/interpretation/variant-interpreter-browser-toolbar.js +++ b/src/webcomponents/variant/interpretation/variant-interpreter-browser-toolbar.js @@ -14,7 +14,7 @@ * limitations under the License. */ -import {LitElement, html} from "lit"; +import {LitElement, html, nothing} from "lit"; import UtilsNew from "../../../core/utils-new.js"; import LitUtils from "../../commons/utils/lit-utils.js"; @@ -23,8 +23,6 @@ class VariantInterpreterBrowserToolbar extends LitElement { constructor() { super(); - - // Set status and init private properties this._init(); } @@ -58,19 +56,22 @@ class VariantInterpreterBrowserToolbar extends LitElement { _init() { this._prefix = UtilsNew.randomString(8); this.write = false; - } - - connectedCallback() { - super.connectedCallback(); - this._config = {...this.getDefaultConfig(), ...this.config}; + this._config = this.getDefaultConfig(); } updated(changedProperties) { if (changedProperties.has("config")) { - this._config = {...this.getDefaultConfig(), ...this.config}; + this._config = { + ...this.getDefaultConfig(), + ...this.config, + }; } } + onFilterPharmacogenomicsVariants() { + LitUtils.dispatchCustomEvent(this, "filterPharmacogenomicsVariants", null); + } + onFilterInclusionVariants() { const variants = []; this.variantInclusionState.map(inclusion => variants.push(...inclusion.variants)); @@ -174,6 +175,15 @@ class VariantInterpreterBrowserToolbar extends LitElement { return html`