diff --git a/end-to-end-tests/specs/screenshot.spec.js b/end-to-end-tests/specs/screenshot.spec.js index a3bbdae24a1..d3a5d8ac00c 100644 --- a/end-to-end-tests/specs/screenshot.spec.js +++ b/end-to-end-tests/specs/screenshot.spec.js @@ -362,6 +362,7 @@ describe("plots tab screenshot tests", function() { }); it("plots tab clinical vs molecular", function() { browser.click('input[data-test="HorizontalAxisClinicalAttributeRadio"]'); + browser.execute(function() { resultsViewPlotsTab.onHorizontalAxisClinicalAttributeSelect({ value: "AGE" }); }); waitForAndCheckPlotsTab(); }); it("plots tab clinical vs molecular boxplot", function() { diff --git a/src/pages/resultsView/plots/PlotsTab.tsx b/src/pages/resultsView/plots/PlotsTab.tsx index b41cdea06e8..d6c3c83d583 100644 --- a/src/pages/resultsView/plots/PlotsTab.tsx +++ b/src/pages/resultsView/plots/PlotsTab.tsx @@ -81,6 +81,10 @@ export interface IPlotsTabProps { store:ResultsViewPageStore; }; +export enum SpecialClinicalAttribute { + TotalMutations = "TOTAL_MUTATIONS", +} + const searchInputTimeoutMs = 600; class PlotsTabScatterPlot extends ScatterPlot {} @@ -420,10 +424,20 @@ export default class PlotsTab extends React.Component { @computed get clinicalAttributeIdToClinicalAttribute():{[clinicalAttributeId:string]:ClinicalAttribute} { if (this.props.store.clinicalAttributes.isComplete) { - return this.props.store.clinicalAttributes.result.reduce((map:{[clinicalAttributeId:string]:ClinicalAttribute}, next)=>{ - map[next.clinicalAttributeId] = next; - return map; - }, {}); + let _map: {[clinicalAttributeId: string]: ClinicalAttribute} = _.keyBy(this.props.store.clinicalAttributes.result, c=>c.clinicalAttributeId) + if (this.props.store.studyIds.isComplete) { + _map[SpecialClinicalAttribute.TotalMutations] = { + clinicalAttributeId: SpecialClinicalAttribute.TotalMutations, + datatype: "NUMBER", + description: "Total mutations", + displayName: "Total mutations", + patientAttribute: false, + priority: "1", + studyId: this.props.store.studyIds.result[0], + count: 0 + }; + }; + return _map; } else { return {}; } @@ -431,10 +445,19 @@ export default class PlotsTab extends React.Component { @computed get clinicalAttributeOptions() { if (this.props.store.clinicalAttributes.isComplete) { - return this.props.store.clinicalAttributes.result.map(attribute=>({ + let _clinicalAttributes: {value: string, label: string}[] = []; + _clinicalAttributes.push({ + value: SpecialClinicalAttribute.TotalMutations, + label: "Total mutations" + }); + + this.props.store.clinicalAttributes.result.map(attribute=>( + _clinicalAttributes.push({ value: attribute.clinicalAttributeId, label: attribute.displayName - })); + }))); + + return _clinicalAttributes; } else { return []; } @@ -585,6 +608,7 @@ export default class PlotsTab extends React.Component { this.props.store.entrezGeneIdToGene, this.props.store.clinicalDataCache, this.props.store.numericGeneMolecularDataCache, + this.props.store.studyToMutationMolecularProfile ); } @@ -596,7 +620,8 @@ export default class PlotsTab extends React.Component { this.props.store.patientKeyToSamples, this.props.store.entrezGeneIdToGene, this.props.store.clinicalDataCache, - this.props.store.numericGeneMolecularDataCache + this.props.store.numericGeneMolecularDataCache, + this.props.store.studyToMutationMolecularProfile ); } diff --git a/src/pages/resultsView/plots/PlotsTabUtils.tsx b/src/pages/resultsView/plots/PlotsTabUtils.tsx index f35fbd62e41..589e6ca7e4a 100644 --- a/src/pages/resultsView/plots/PlotsTabUtils.tsx +++ b/src/pages/resultsView/plots/PlotsTabUtils.tsx @@ -1,4 +1,4 @@ -import {AxisMenuSelection, AxisType, ViewType} from "./PlotsTab"; +import {AxisMenuSelection, AxisType, ViewType, SpecialClinicalAttribute} from "./PlotsTab"; import {MobxPromise} from "mobxpromise"; import { CancerStudy, @@ -30,6 +30,7 @@ import {IBoxScatterPlotData} from "../../../shared/components/plots/BoxScatterPl import {AlterationTypeConstants, AnnotatedMutation} from "../ResultsViewPageStore"; import numeral from "numeral"; import {getUniqueSampleKeyToCategories} from "../../../shared/components/plots/TablePlotUtils"; +import client from "../../../shared/api/cbioportalClientInstance"; export const molecularProfileTypeToDisplayType:{[s:string]:string} = { "COPY_NUMBER_ALTERATION": "Copy Number", @@ -306,44 +307,83 @@ function makeAxisDataPromise_Clinical( attribute:ClinicalAttribute, clinicalDataCache:MobxPromiseCache, patientKeyToSamples:MobxPromise<{[uniquePatientKey:string]:Sample[]}>, + studyToMutationMolecularProfile: MobxPromise<{[studyId: string]: MolecularProfile}> ):MobxPromise { const promise = clinicalDataCache.get(attribute); - return remoteData({ - await:()=>[promise, patientKeyToSamples], - invoke:()=>{ - const _patientKeyToSamples = patientKeyToSamples.result!; - const data:ClinicalData[] = promise.result!; - const axisData:IAxisData = { data:[], datatype:attribute.datatype.toLowerCase() }; - const shouldParseFloat = attribute.datatype.toLowerCase() === "number"; - const axisData_Data = axisData.data; - if (attribute.patientAttribute) { - // produce sample data from patient clinical data - for (const d of data) { - const samples = _patientKeyToSamples[d.uniquePatientKey]; - for (const sample of samples) { + let ret:MobxPromise; + switch(attribute.clinicalAttributeId) { + case SpecialClinicalAttribute.TotalMutations: + let mutationCounts = remoteData({ + await:()=>[patientKeyToSamples, studyToMutationMolecularProfile], + invoke:()=>{ + const _patientKeyToSamples = patientKeyToSamples.result!; + const _studyToMutationMolecularProfile = studyToMutationMolecularProfile.result!; + // get all samples + let samples = _.flatten(_.values(_patientKeyToSamples)); + // produce sample data from patient clinical data + let mutationCounts = client.fetchMutationCountsInMolecularProfileUsingPOST({ + molecularProfileId: _studyToMutationMolecularProfile[attribute.studyId].molecularProfileId, + sampleIds: samples.map(s=>s.sampleId) + }); + return Promise.resolve(mutationCounts); + } + }); + ret = remoteData({ + await:()=>[mutationCounts], + invoke:()=>{ + const _mutationCounts = mutationCounts.result!; + const axisData:IAxisData = { data:[], datatype:attribute.datatype.toLowerCase() }; + const axisData_Data = axisData.data; + for (const mutationCount of _mutationCounts) { axisData_Data.push({ - uniqueSampleKey: sample.uniqueSampleKey, - value: d.value, + uniqueSampleKey: mutationCount.uniqueSampleKey, + value: mutationCount.mutationCount, }); } + return Promise.resolve(axisData); } - } else { - // produce sample data from sample clinical data - for (const d of data) { - axisData_Data.push({ - uniqueSampleKey: d.uniqueSampleKey, - value: d.value - }); - } - } - if (shouldParseFloat) { - for (const d of axisData_Data) { - d.value = parseFloat(d.value as string); // we know its a string bc all clinical data comes back as string + }); + break; + default: + ret = remoteData({ + await:()=>[promise, patientKeyToSamples], + invoke:()=>{ + const _patientKeyToSamples = patientKeyToSamples.result!; + const data:ClinicalData[] = promise.result!; + const axisData:IAxisData = { data:[], datatype:attribute.datatype.toLowerCase() }; + const shouldParseFloat = attribute.datatype.toLowerCase() === "number"; + const axisData_Data = axisData.data; + if (attribute.patientAttribute) { + // produce sample data from patient clinical data + for (const d of data) { + const samples = _patientKeyToSamples[d.uniquePatientKey]; + for (const sample of samples) { + axisData_Data.push({ + uniqueSampleKey: sample.uniqueSampleKey, + value: d.value, + }); + } + } + } else { + // produce sample data from sample clinical data + for (const d of data) { + axisData_Data.push({ + uniqueSampleKey: d.uniqueSampleKey, + value: d.value + }); + } + } + if (shouldParseFloat) { + for (const d of axisData_Data) { + d.value = parseFloat(d.value as string); // we know its a string bc all clinical data comes back as string + } + } + return Promise.resolve(axisData); } - } - return Promise.resolve(axisData); - } - }); + }); + break; + } + return ret; } function makeAxisDataPromise_Molecular( @@ -393,7 +433,9 @@ export function makeAxisDataPromise( patientKeyToSamples:MobxPromise<{[uniquePatientKey:string]:Sample[]}>, entrezGeneIdToGene:MobxPromise<{[entrezGeneId:number]:Gene}>, clinicalDataCache:MobxPromiseCache, - numericGeneMolecularDataCache:MobxPromiseCache<{entrezGeneId:number, molecularProfileId:string}, NumericGeneMolecularData[]> + numericGeneMolecularDataCache:MobxPromiseCache<{entrezGeneId:number, molecularProfileId:string}, NumericGeneMolecularData[]>, + studyToMutationMolecularProfile: MobxPromise<{[studyId: string]: MolecularProfile}> + ):MobxPromise { let ret:MobxPromise = remoteData(()=>new Promise(()=>0)); // always isPending @@ -401,7 +443,7 @@ export function makeAxisDataPromise( case AxisType.clinicalAttribute: if (selection.clinicalAttributeId !== undefined) { const attribute = clinicalAttributeIdToClinicalAttribute[selection.clinicalAttributeId]; - ret = makeAxisDataPromise_Clinical(attribute, clinicalDataCache, patientKeyToSamples); + ret = makeAxisDataPromise_Clinical(attribute, clinicalDataCache, patientKeyToSamples, studyToMutationMolecularProfile); } break; case AxisType.molecularProfile: