diff --git a/src/sites/iva/conf/browsers.settings.js b/src/sites/iva/conf/browsers.settings.js
index 22bd3d59b..8ed956c77 100644
--- a/src/sites/iva/conf/browsers.settings.js
+++ b/src/sites/iva/conf/browsers.settings.js
@@ -767,9 +767,6 @@ const INTERPRETER_SETTINGS = {
},
// hideGenomeBrowser: false
},
- {
- id: "review"
- },
{
id: "report"
}
diff --git a/src/sites/iva/conf/variant-interpreter.settings.js b/src/sites/iva/conf/variant-interpreter.settings.js
index 07df252cc..f8909d17e 100644
--- a/src/sites/iva/conf/variant-interpreter.settings.js
+++ b/src/sites/iva/conf/variant-interpreter.settings.js
@@ -31,9 +31,6 @@ const VARIANT_INTERPRETER_SETTINGS = {
},
// hideGenomeBrowser: false
},
- {
- id: "review"
- },
{
id: "report"
}
diff --git a/src/webcomponents/clinical/clinical-analysis-manager.js b/src/webcomponents/clinical/clinical-analysis-manager.js
index 59cfa8c65..c203cebb2 100644
--- a/src/webcomponents/clinical/clinical-analysis-manager.js
+++ b/src/webcomponents/clinical/clinical-analysis-manager.js
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import UtilsNew from "../../core/utils-new.js";
import LitUtils from "../commons/utils/lit-utils.js";
import NotificationUtils from "../commons/utils/notification-utils.js";
@@ -283,6 +284,19 @@ export default class ClinicalAnalysisManager {
this.#updateInterpretation(interpretationId, {locked: false}, `Interpretation '${interpretationId}' Unlocked.`, callback);
}
+ downloadInterpretation(interpretationId) {
+ return this.opencgaSession.opencgaClient.clinical()
+ .infoInterpretation(interpretationId, {
+ study: this.opencgaSession.study.fqn,
+ })
+ .then(response => {
+ UtilsNew.downloadJSON(response?.responses?.[0]?.results?.[0], `interpretation-${interpretationId}.json`);
+ })
+ .catch(response => {
+ NotificationUtils.dispatch(this.ctx, NotificationUtils.NOTIFY_RESPONSE, response);
+ });
+ }
+
updateVariant(variant, interpretation, callback) {
this.opencgaSession.opencgaClient.clinical().updateInterpretation(this.clinicalAnalysis.id, interpretation.id, {primaryFindings: [variant]}, {
study: this.opencgaSession.study.fqn,
diff --git a/src/webcomponents/clinical/interpretation/clinical-interpretation-manager.js b/src/webcomponents/clinical/interpretation/clinical-interpretation-manager.js
index 672248190..9fc98e24b 100644
--- a/src/webcomponents/clinical/interpretation/clinical-interpretation-manager.js
+++ b/src/webcomponents/clinical/interpretation/clinical-interpretation-manager.js
@@ -14,12 +14,11 @@
* limitations under the License.
*/
-import {LitElement, html} from "lit";
+import {LitElement, html, nothing} from "lit";
import {classMap} from "lit/directives/class-map.js";
import ClinicalAnalysisManager from "../clinical-analysis-manager.js";
import UtilsNew from "../../../core/utils-new.js";
import LitUtils from "../../commons/utils/lit-utils.js";
-import GridCommons from "../../commons/grid-commons.js";
import "./clinical-interpretation-summary.js";
import "./clinical-interpretation-create.js";
import "./clinical-interpretation-update.js";
@@ -31,7 +30,7 @@ export default class ClinicalInterpretationManager extends LitElement {
super();
// Set status and init private properties
- this._init();
+ this.#init();
}
createRenderRoot() {
@@ -55,30 +54,21 @@ export default class ClinicalInterpretationManager extends LitElement {
};
}
- _init() {
+ #init() {
this._prefix = UtilsNew.randomString(8);
-
- this.gridId = this._prefix + "Grid";
- this.interpretationVersions = [];
- }
-
- connectedCallback() {
- super.connectedCallback();
-
- this._config = {...this.getDefaultConfig(), ...this.config};
- this.gridCommons = new GridCommons(this.gridId, this, this._config);
- this.clinicalAnalysisManager = new ClinicalAnalysisManager(this, this.clinicalAnalysis, this.opencgaSession);
+ this._config = this.getDefaultConfig();
+ this.clinicalAnalysisManager = null;
}
update(changedProperties) {
- if (changedProperties.has("clinicalAnalysis")) {
- this.clinicalAnalysisObserver();
- }
if (changedProperties.has("clinicalAnalysisId")) {
this.clinicalAnalysisIdObserver();
}
if (changedProperties.has("opencgaSession") || changedProperties.has("config")) {
- this._config = {...this.getDefaultConfig(), ...this.config};
+ this._config = {
+ ...this.getDefaultConfig(),
+ ...this.config,
+ };
this.clinicalAnalysisManager = new ClinicalAnalysisManager(this, this.clinicalAnalysis, this.opencgaSession);
}
super.update(changedProperties);
@@ -97,45 +87,13 @@ export default class ClinicalInterpretationManager extends LitElement {
}
}
- clinicalAnalysisObserver() {
- if (this.clinicalAnalysis && this.clinicalAnalysis.interpretation) {
- this.clinicalAnalysisManager = new ClinicalAnalysisManager(this, this.clinicalAnalysis, this.opencgaSession);
-
- // this.interpretations = [
- // {
- // ...this.clinicalAnalysis.interpretation, primary: true
- // },
- // ...this.clinicalAnalysis.secondaryInterpretations
- // ];
-
- const params = {
- study: this.opencgaSession.study.fqn,
- version: "all",
- };
- this.opencgaSession.opencgaClient.clinical().infoInterpretation(this.clinicalAnalysis.interpretation.id, params)
- .then(response => {
- this.interpretationVersions = response.responses[0].results.reverse();
-
- // We always refresh UI when clinicalAnalysisObserver is called
- // await this.updateComplete;
- this.requestUpdate();
- this.renderHistoryTable();
- })
- .catch(response => {
- console.error("An error occurred fetching clinicalAnalysis: ", response);
- });
- }
- }
-
renderInterpretation(interpretation, primary) {
- const interpretationLockAction = interpretation.locked ?
- this.renderItemAction(interpretation, "unlock", "fa-unlock", "Unlock") :
- this.renderItemAction(interpretation, "lock", "fa-lock", "Lock");
+ const locked = interpretation?.locked;
const interpretationTitle = interpretation.locked ?
html` Interpretation #${interpretation.id.split(".")[1]} - ${interpretation.id}`:
html`Interpretation #${interpretation.id.split(".")[1]} - ${interpretation.id}`;
- const editInterpretationTitle = `Edit interpretation #${interpretation.id.split(".")[1]}: ${interpretation.id}`;
+ const editInterpretationTitle = `Edit Interpretation #${interpretation.id.split(".")[1]}: ${interpretation.id}`;
return html`
@@ -170,35 +128,20 @@ export default class ClinicalInterpretationManager extends LitElement {
-
@@ -212,36 +155,17 @@ export default class ClinicalInterpretationManager extends LitElement {
`;
}
- renderHistoryTable() {
- this.table = $("#" + this.gridId);
- this.table.bootstrapTable("destroy");
- this.table.bootstrapTable({
- theadClasses: "table-light",
- buttonsClass: "light",
- data: this.interpretationVersions,
- columns: this._initTableColumns(),
- uniqueId: "id",
- iconsPrefix: GridCommons.GRID_ICONS_PREFIX,
- icons: GridCommons.GRID_ICONS,
- gridContext: this,
- sidePagination: "local",
- pagination: true,
- formatNoMatches: () => "No previous versions",
- // formatLoadingMessage: () => "
",
- loadingTemplate: () => GridCommons.loadingFormatter(),
- onClickRow: (row, selectedElement) => this.gridCommons.onClickRow(row.id, row, selectedElement),
- });
- }
-
- renderItemAction(interpretation, action, icon, name) {
+ renderItemAction(interpretation, action, icon, name, defaultDisabled = false) {
+ const disabled = defaultDisabled || (interpretation?.locked && ((action !== "unlock") && (action !== "setAsPrimary")));
return html`
${name}
@@ -249,57 +173,15 @@ export default class ClinicalInterpretationManager extends LitElement {
`;
}
- _initTableColumns() {
- this._columns = [
- {
- title: "ID",
- field: "id"
- },
- {
- title: "Version",
- field: "version"
- },
- {
- title: "Modification Date",
- field: "modificationDate",
- formatter: modificationDate => UtilsNew.dateFormatter(modificationDate, "D MMM YYYY, h:mm:ss a")
- },
- {
- title: "Primary Findings",
- field: "primaryFindings",
- formatter: primaryFindings => primaryFindings?.length
- },
- {
- title: "Status",
- field: "internal.status.name"
- },
- {
- title: "Actions",
- formatter: () => `
-
- View
- Restore
-
- `,
- valign: "middle",
- events: {
- "click button": this.onActionClick.bind(this)
- },
- visible: !this._config.columns?.hidden?.includes("actions")
- }
- ];
-
- return this._columns;
- }
-
onActionClick(e) {
+ e.preventDefault();
const {action, interpretationId, islocked} = e.currentTarget.dataset;
const interpretationCallback = () => {
this.onClinicalInterpretationUpdate();
};
- // islock is a strring
- if (islocked === "true" && ((action !== "unlock") && (action !== "setAsPrimary"))) {
+ // Only some actions are allowed when the interpretation is locked: unclock, set as primary, and download
+ if (islocked === "true" && ((action !== "unlock") && (action !== "setAsPrimary") && (action !== "download"))) {
NotificationUtils.dispatch(this, NotificationUtils.NOTIFY_WARNING, {
message: `${interpretationId} is locked!`,
});
@@ -320,6 +202,9 @@ export default class ClinicalInterpretationManager extends LitElement {
case "unlock":
this.clinicalAnalysisManager.unLockInterpretation(interpretationId, interpretationCallback);
break;
+ case "download":
+ this.clinicalAnalysisManager.downloadInterpretation(interpretationId);
+ break;
}
}
}
@@ -381,11 +266,6 @@ export default class ClinicalInterpretationManager extends LitElement {
`}
-
-
-
Primary Interpretation History - ${this.clinicalAnalysis.interpretation.id}
-
-
`;
diff --git a/src/webcomponents/clinical/interpretation/clinical-interpretation-update.js b/src/webcomponents/clinical/interpretation/clinical-interpretation-update.js
index 9f6d3c06e..19e25ac38 100644
--- a/src/webcomponents/clinical/interpretation/clinical-interpretation-update.js
+++ b/src/webcomponents/clinical/interpretation/clinical-interpretation-update.js
@@ -176,6 +176,11 @@ export default class ClinicalInterpretationUpdate extends LitElement {
}
}
},
+ {
+ title: "Lock",
+ type: "toggle-switch",
+ field: "locked",
+ },
{
title: "Disease Panels",
field: "panels",
@@ -197,7 +202,7 @@ export default class ClinicalInterpretationUpdate extends LitElement {
.diseasePanels="${panelList}"
.panel="${panels?.map(panel => panel.id).join(",")}"
.showExtendedFilters="${false}"
- .showSelectedPanels="${false}"
+ .showSelectedPanels="${true}"
.classes="${updateParams.panels ? "selection-updated" : ""}"
.disabled="${panelLock}"
@filterChange="${e => handlePanelsFilterChange(e)}">
diff --git a/src/webcomponents/variant/interpretation/variant-interpreter-browser-save.js b/src/webcomponents/variant/interpretation/variant-interpreter-browser-save.js
new file mode 100644
index 000000000..4c36da11f
--- /dev/null
+++ b/src/webcomponents/variant/interpretation/variant-interpreter-browser-save.js
@@ -0,0 +1,127 @@
+import {LitElement, html} from "lit";
+import UtilsNew from "../../../core/utils-new.js";
+import LitUtils from "../../commons/utils/lit-utils.js";
+
+class VariantInterpreterBrowserSave extends LitElement {
+
+ constructor() {
+ super();
+ this.#init();
+ }
+
+ createRenderRoot() {
+ return this;
+ }
+
+ static get properties() {
+ return {
+ opencgaSession: {
+ type: Object,
+ },
+ clinicalAnalysis: {
+ type: Object,
+ },
+ state: {
+ type: Object,
+ },
+ };
+ }
+
+ #init() {
+ this._prefix = UtilsNew.randomString(8);
+ }
+
+ onSave() {
+ // 1. Dispatch evnet to save variants and include the (optional) comment
+ LitUtils.dispatchCustomEvent(this, "saveVariants", null, {
+ comment: {
+ message: this.querySelector(`textarea#${this._prefix}CommentMessage`).value || "",
+ tags: (this.querySelector(`input#${this._prefix}CommentTags`).value || "").trim().split(",").map(t => t.trim()).filter(Boolean),
+ },
+ });
+ // 2. Reset comment fields
+ this.querySelector(`textarea#${this._prefix}CommentMessage`).value = "";
+ this.querySelector(`input#${this._prefix}CommentTags`).value = "";
+ }
+
+ onDiscard() {
+ LitUtils.dispatchCustomEvent(this, "discardVariants", null);
+ }
+
+ onFilter() {
+ LitUtils.dispatchCustomEvent(this, "filterVariants", null);
+ }
+
+ renderVariant(variant, color) {
+ const geneNames = Array.from(new Set(variant.annotation.consequenceTypes.filter(ct => ct.geneName).map(ct => ct.geneName)));
+ return html`
+
+
${variant.id} ${variant.annotation.displayConsequenceType || ""}
+
${geneNames.join(", ")}
+
+ `;
+ }
+
+ renderVariantsList(title, variants, color) {
+ return html`
+ ${title} (${variants.length})
+
+ ${variants.map(variant => this.renderVariant(variant, color))}
+
+ `;
+ }
+
+ render() {
+ const hasVariantsToSave = this.state.addedVariants?.length || this.state.removedVariants?.length || this.state.updatedVariants?.length;
+ const hasVariantsToFilter = this.state.addedVariants?.length || this.state.updatedVariants?.length;
+ return html`
+
+
+ Changed Variants
+
+
+ ${this.renderVariantsList("New selected variants", this.state?.addedVariants || [], "success")}
+ ${this.renderVariantsList("Updated variants", this.state?.updatedVariants || [], "warning")}
+ ${this.renderVariantsList("Removed variants", this.state?.removedVariants || [], "danger")}
+
+
+
+ Add a new Interpretation Comment
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ this.onFilter()}">
+ Filter Variants
+
+
+
+ this.onDiscard()}">
+ Discard Changes
+
+ this.onSave()}">
+ Save
+
+
+
+
+ `;
+ }
+
+ getDefaultConfig() {
+ return {};
+ }
+
+}
+
+customElements.define("variant-interpreter-browser-save", VariantInterpreterBrowserSave);
diff --git a/src/webcomponents/variant/interpretation/variant-interpreter-browser-template.js b/src/webcomponents/variant/interpretation/variant-interpreter-browser-template.js
index cfdb33ba6..b118a7f3e 100644
--- a/src/webcomponents/variant/interpretation/variant-interpreter-browser-template.js
+++ b/src/webcomponents/variant/interpretation/variant-interpreter-browser-template.js
@@ -243,10 +243,10 @@ class VariantInterpreterBrowserTemplate extends LitElement {
onFilterVariants(e) {
const lockedFields = [...this._config?.filter?.activeFilters?.lockedFields.map(key => key.id), "study"];
- const variantIds = e.detail.variants.map(v => v.id);
+ const variantIds = new Set(e.detail.variants.map(v => v.id));
this.query = {
...UtilsNew.filterKeys(this.executedQuery, lockedFields),
- id: variantIds.join(","),
+ id: Array.from(variantIds).join(","),
};
this.notifyQueryChange();
this.requestUpdate();
diff --git a/src/webcomponents/variant/interpretation/variant-interpreter-browser-toolbar.js b/src/webcomponents/variant/interpretation/variant-interpreter-browser-toolbar.js
index c668a5d84..694bcc496 100644
--- a/src/webcomponents/variant/interpretation/variant-interpreter-browser-toolbar.js
+++ b/src/webcomponents/variant/interpretation/variant-interpreter-browser-toolbar.js
@@ -14,16 +14,16 @@
* 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";
+import "./variant-interpreter-browser-save.js";
class VariantInterpreterBrowserToolbar extends LitElement {
constructor() {
super();
- // Set status and init private properties
- this._init();
+ this.#init();
}
createRenderRoot() {
@@ -53,20 +53,20 @@ class VariantInterpreterBrowserToolbar extends LitElement {
};
}
- _init() {
+ #init() {
this._prefix = UtilsNew.randomString(8);
this.write = false;
+ this._config = this.getDefaultConfig();
}
- connectedCallback() {
- super.connectedCallback();
- this._config = {...this.getDefaultConfig(), ...this.config};
- }
-
- updated(changedProperties) {
+ update(changedProperties) {
if (changedProperties.has("config")) {
- this._config = {...this.getDefaultConfig(), ...this.config};
+ this._config = {
+ ...this.getDefaultConfig(),
+ ...this.config,
+ };
}
+ super.update(changedProperties);
}
onFilterInclusionVariants() {
@@ -81,26 +81,38 @@ class VariantInterpreterBrowserToolbar extends LitElement {
LitUtils.dispatchCustomEvent(this, "filterVariants", null, {
variants: this.clinicalAnalysis.interpretation.primaryFindings,
});
+ // Josemi 20240701 NOTE: this is a terrible and temporal fix to force closing the Save Menu
+ // when user clicks the 'Filter' button in the View menu (primary findings).
+ this.querySelector(`div#${this._prefix}View ul.dropdown-menu`)?.classList?.toggle?.("show");
}
onFilterModifiedVariants() {
LitUtils.dispatchCustomEvent(this, "filterVariants", null, {
variants: [
...this.state.addedVariants,
+ ...this.state.updatedVariants,
...this.state.removedVariants,
],
});
+ // Josemi 20240701 NOTE: this is a terrible and temporal fix to force closing the Save Menu
+ // when user clicks the 'Filter Variants' button in the Save menu.
+ this.querySelector(`div#${this._prefix}Save ul.dropdown-menu`)?.classList?.toggle?.("show");
}
onResetModifiedVariants() {
LitUtils.dispatchCustomEvent(this, "resetVariants", null);
+ // Josemi 20240701 NOTE: this is a terrible and temporal fix to force closing the Save Menu
+ // when user clicks the 'Discard Changes' button in the Save menu.
+ this.querySelector(`div#${this._prefix}Save ul.dropdown-menu`)?.classList?.toggle?.("show");
}
- onSaveInterpretation() {
+ onSaveInterpretation(event) {
LitUtils.dispatchCustomEvent(this, "saveInterpretation", null, {
- comment: this.comment
+ comment: event?.detail?.comment || {},
});
- this.comment = {};
+ // Josemi 20240701 NOTE: this is a terrible and temporal fix to force closing the Save Menu
+ // when user clicks the 'Save' button in the Save menu.
+ this.querySelector(`div#${this._prefix}Save ul.dropdown-menu`)?.classList?.toggle?.("show");
}
onSaveFieldsChange(type, e) {
@@ -115,6 +127,26 @@ class VariantInterpreterBrowserToolbar extends LitElement {
}
}
+ // onInterpretationChangesModalShow() {
+ // ModalUtils.show();
+ // }
+
+ // renderInterpretationChangesSaveModal() {
+ // return ModalUtils.create(this, `${this._prefix}InterpretationChangesSaveModal`, {
+ // display: {
+ // modalTitle: "Review and Save Interpretation Changes",
+ // modalDraggable: false,
+ // modalSize: "modal-lg"
+ // },
+ // render: () => html`
+ //
+ //
+ // `,
+ // });
+ // }
+
renderInclusionVariant(inclusion) {
const iconHtml = html`
${variant.id}
- Genotype: ${GT} (${FILTER})
+ Genotype: ${GT} (${FILTER})
`;
}) : html `No variants found.
`
@@ -152,22 +184,20 @@ class VariantInterpreterBrowserToolbar extends LitElement {
`;
}
- renderVariant(variant, icon) {
+ renderVariant(variant) {
const geneNames = Array.from(new Set(variant.annotation.consequenceTypes.filter(ct => ct.geneName).map(ct => ct.geneName)));
- const iconHtml = icon ? html`` : "";
return html`
-
-
${variant.id} (${variant.type}) ${iconHtml}
-
${variant.annotation.displayConsequenceType}
-
${geneNames.join(", ")}
+
+
${variant.id} ${variant.annotation.displayConsequenceType || ""}
+
${geneNames.join(", ")}
`;
}
render() {
- const primaryFindings = this.clinicalAnalysis.interpretation?.primaryFindings;
const hasVariantsToSave = this.state.addedVariants?.length || this.state.removedVariants?.length || this.state.updatedVariants?.length;
+ const primaryFindings = this.clinicalAnalysis?.interpretation?.primaryFindings || [];
return html`
@@ -204,19 +234,17 @@ class VariantInterpreterBrowserToolbar extends LitElement {
-
-