Skip to content

Commit

Permalink
Merge pull request #938 from opencb/TASK-5963
Browse files Browse the repository at this point in the history
TASK-5963 - Clinical Interpretation Improvements
  • Loading branch information
jmjuanes authored Jul 10, 2024
2 parents cd2c831 + ed1343b commit 90b5a74
Show file tree
Hide file tree
Showing 10 changed files with 341 additions and 322 deletions.
3 changes: 0 additions & 3 deletions src/sites/iva/conf/browsers.settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -767,9 +767,6 @@ const INTERPRETER_SETTINGS = {
},
// hideGenomeBrowser: false
},
{
id: "review"
},
{
id: "report"
}
Expand Down
3 changes: 0 additions & 3 deletions src/sites/iva/conf/variant-interpreter.settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ const VARIANT_INTERPRETER_SETTINGS = {
},
// hideGenomeBrowser: false
},
{
id: "review"
},
{
id: "report"
}
Expand Down
14 changes: 14 additions & 0 deletions src/webcomponents/clinical/clinical-analysis-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -31,7 +30,7 @@ export default class ClinicalInterpretationManager extends LitElement {
super();

// Set status and init private properties
this._init();
this.#init();
}

createRenderRoot() {
Expand All @@ -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);
Expand All @@ -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`<i class="fas fa-lock"></i> 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`
<div class="d-flex pb-1">
Expand Down Expand Up @@ -170,35 +128,20 @@ export default class ClinicalInterpretationManager extends LitElement {
</clinical-interpretation-update>
<div class="dropdown">
<button class="btn btn-light dropdown-toggle one-line" type="button" data-bs-toggle="dropdown"
?disabled="${this.clinicalAnalysis.locked}">
Action
<button class="btn btn-light dropdown-toggle" type="button" data-bs-toggle="dropdown" ?disabled="${this.clinicalAnalysis.locked}">
<i class="fas fa-toolbox pe-1"></i>
Actions
</button>
<ul class="dropdown-menu">
${primary ? html`
<li>
<a
class="dropdown-item disabled"
data-action="restorePrevious"
data-interpretation-id="${interpretation.id}"
data-islocked="${interpretation.locked}"
@click="${this.onActionClick}">
<i class="fas fa-code-branch me-1" aria-hidden="true"></i>
Restore previous version
</a>
</li>
<!-- Action Lock/Unlock -->
${interpretationLockAction}
<li><hr class="dropdown-divider"></li>
${this.renderItemAction(interpretation, "clear", "fa-eraser", "Clear")}
` : html`
<ul class="dropdown-menu dropdown-menu-end">
${this.renderItemAction(interpretation, "download", "fa-download", "Download JSON")}
<li><hr class="dropdown-divider"></li>
${!primary ? html`
${this.renderItemAction(interpretation, "setAsPrimary", "fa-map-marker", "Set as primary")}
<!-- Action Lock/Unlock -->
${interpretationLockAction}
<li><hr class="dropdown-divider"></li>
${this.renderItemAction(interpretation, "clear", "fa-eraser", "Clear")}
${this.renderItemAction(interpretation, "delete", "fa-trash", "Delete")}
`}
` : nothing}
${this.renderItemAction(interpretation, locked ? "unlock" : "lock", locked ? "fa-unlock" : "fa-lock", locked ? "Unlock" : "Lock")}
<li><hr class="dropdown-divider"></li>
${this.renderItemAction(interpretation, "clear", "fa-eraser", "Clear")}
${this.renderItemAction(interpretation, "delete", "fa-trash", "Delete", primary)}
</ul>
</div>
</div>
Expand All @@ -212,94 +155,33 @@ 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: () => "<div><loading-spinner></loading-spinner></div>",
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`
<li>
<a
class="dropdown-item"
?disabled="${interpretation.locked && ((action !== "unlock") && (action !== "setAsPrimary"))}"
class="${`dropdown-item ${disabled ? "disabled" : ""}`}"
?disabled="${disabled}"
data-action="${action}"
data-interpretation-id="${interpretation.id}"
data-islocked="${interpretation.locked}"
style="cursor:pointer;"
@click="${this.onActionClick}">
<i class="fas ${icon} me-1" aria-hidden="true"></i> ${name}
</a>
</li>
`;
}

_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: () => `
<div class="btn-group">
<button class="btn btn-link link-underline link-underline-opacity-0 link-underline-opacity-75-hover" disabled type="button" data-action="view">View</button>
<button class="btn btn-link link-underline link-underline-opacity-0 link-underline-opacity-75-hover" type="button" data-action="restore">Restore</button>
</div>
`,
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!`,
});
Expand All @@ -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;
}
}
}
Expand Down Expand Up @@ -381,11 +266,6 @@ export default class ClinicalInterpretationManager extends LitElement {
<label>No secondary interpretations found</label>
`}
</div>
<div class="col-md-10 pt-2">
<h3>Primary Interpretation History - ${this.clinicalAnalysis.interpretation.id}</h3>
<table id="${this.gridId}"></table>
</div>
</div>
</div>
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,11 @@ export default class ClinicalInterpretationUpdate extends LitElement {
}
}
},
{
title: "Lock",
type: "toggle-switch",
field: "locked",
},
{
title: "Disease Panels",
field: "panels",
Expand All @@ -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)}">
Expand Down
Loading

0 comments on commit 90b5a74

Please sign in to comment.