Skip to content

Commit

Permalink
lazy fetch clinical attributes instead of all on page load
Browse files Browse the repository at this point in the history
  • Loading branch information
Rajat-Sirohi committed Jun 15, 2021
1 parent 8f2a649 commit 4b0b8a8
Show file tree
Hide file tree
Showing 70 changed files with 616 additions and 54 deletions.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Expand Up @@ -32,7 +32,7 @@ export default class TableCellStatusIndicator extends React.Component<
break;
case TableCellStatus.NA:
alt = this.props.naAlt || 'Data not available.';
text = '';
text = 'n/a';
break;
}
return (
Expand Down
27 changes: 27 additions & 0 deletions src/pages/resultsView/ResultsViewPageStore.ts
Expand Up @@ -3442,6 +3442,9 @@ export class ResultsViewPageStore {
this.uniqueSampleKeyToTumorType.result!,
this.generateGenomeNexusHgvsgUrl,
this.clinicalDataGroupedBySampleMap,
this.mutationsTabClinicalAttributes,
this.clinicalAttributeIdToAvailableSampleCount,
this.sampleCount,
this.genomeNexusClient,
this.genomeNexusInternalClient,
() => this.urlWrapper.query.mutations_transcript_id
Expand Down Expand Up @@ -3586,6 +3589,25 @@ export class ResultsViewPageStore {
{}
);

readonly mutationsTabClinicalAttributes = remoteData<ClinicalAttribute[]>({
await: () => [this.studyIds],
invoke: async () => {
const clinicalAttributes = await client.fetchClinicalAttributesUsingPOST(
{
studyIds: this.studyIds.result!,
}
);
const excludeList = ['CANCER_TYPE_DETAILED', 'MUTATION_COUNT'];

return _.uniqBy(
clinicalAttributes.filter(
x => !excludeList.includes(x.clinicalAttributeId)
),
x => x.clinicalAttributeId
);
},
});

private getClinicalData(
clinicalDataType: 'SAMPLE' | 'PATIENT',
studies: any[],
Expand Down Expand Up @@ -3647,6 +3669,11 @@ export class ResultsViewPageStore {
[]
);

readonly sampleCount = remoteData<number>({
await: () => [this.samples],
invoke: () => Promise.resolve(this.samples.result!.length),
});

readonly samples = remoteData(
{
await: () => [this.studyToDataQueryFilter],
Expand Down
270 changes: 270 additions & 0 deletions src/pages/resultsView/mutation/AddColumns.tsx
@@ -0,0 +1,270 @@
import * as React from 'react';
import * as _ from 'lodash';
import { Checkbox } from 'react-bootstrap';
import { observer } from 'mobx-react';
import { action, computed, makeObservable, observable } from 'mobx';
import { remoteData, getTextWidth } from 'cbioportal-frontend-commons';
import { ClinicalAttribute } from 'cbioportal-ts-api-client';
import { IColumnVisibilityControlsProps } from 'shared/components/columnVisibilityControls/ColumnVisibilityControls';
import { MSKTab, MSKTabs } from 'shared/components/MSKTabs/MSKTabs';
import CustomDropdown from 'shared/components/oncoprint/controls/CustomDropdown';
import AddChartByType from '../../studyView/addChartButton/addChartByType/AddChartByType';
import { ChartDataCountSet } from '../../studyView/StudyViewUtils';

export interface IAddColumnsProps extends IColumnVisibilityControlsProps {
clinicalAttributes: ClinicalAttribute[];
clinicalAttributeIdToAvailableSampleCount: { [id: string]: number };
sampleCount: number;
}

enum Tab {
MUTATIONS = 'Mutations',
CLINICAL = 'Clinical',
}

type Option = {
key: string;
label: string;
selected: boolean;
};

// Copied these constants from oncoprint/AddTracks.tsx
// since importing them results in errors during testing
// due to a separate import in that file.
const MIN_DROPDOWN_WIDTH = 400;
const CONTAINER_PADDING_WIDTH = 20;
const TAB_PADDING_WIDTH = 14;
const COUNT_PADDING_WIDTH = 17;
@observer
export default class AddColumns extends React.Component<IAddColumnsProps, {}> {
@observable tabId: Tab = Tab.MUTATIONS;

constructor(props: IAddColumnsProps) {
super(props);
makeObservable(this);
}

@action.bound
private updateTabId(newId: Tab) {
this.tabId = newId;
}

@action.bound
private addAll(ids: string[], options: Option[] | undefined) {
if (this.props.onColumnToggled && options) {
for (let option of options) {
if (!option.selected && ids.includes(option.key)) {
this.props.onColumnToggled(option.key);
}
}
}
}

@action.bound
private clearAll(ids: string[], options: Option[] | undefined) {
if (this.props.onColumnToggled && options) {
for (let option of options) {
if (option.selected && ids.includes(option.key)) {
this.props.onColumnToggled(option.key);
}
}
}
}

@action.bound
private toggle(id: string) {
if (this.props.onColumnToggled && id) {
this.props.onColumnToggled(id);
}
}

readonly emptyPromise = remoteData({
invoke: () =>
new Promise<ChartDataCountSet>(() => {
return;
}),
});

readonly clinicalAttributeIdToAvailableFrequencyPromise = remoteData({
invoke: () =>
Promise.resolve(
_.mapValues(
this.props.clinicalAttributeIdToAvailableSampleCount,
count => (100 * count) / this.props.sampleCount
)
),
});

@computed get clinicalAttributeIds(): Set<string> {
let ids: Set<string> = new Set();
this.props.clinicalAttributes.forEach(x =>
ids.add(x.clinicalAttributeId)
);
return ids.add('CANCER_TYPE_DETAILED').add('MUTATION_COUNT');
}

@computed get mutationsOptions(): Option[] | undefined {
return this.props.columnVisibility
?.filter(col => !this.clinicalAttributeIds.has(col.id))
.map(col => ({
key: col.id,
label: col.name,
selected: col.visible,
}));
}

@computed get mutationsTabContent() {
return (
<div>
{this.mutationsOptions && (
<AddChartByType
options={this.mutationsOptions}
freqPromise={this.emptyPromise}
onAddAll={(ids: string[]) =>
this.addAll(ids, this.mutationsOptions)
}
onClearAll={(ids: string[]) =>
this.clearAll(ids, this.mutationsOptions)
}
onToggleOption={this.toggle}
optionsGivenInSortedOrder={true}
width={this.dropdownWidth}
excludeFrequency={true}
/>
)}
</div>
);
}

@computed get clinicalOptions(): Option[] | undefined {
if (!this.props.columnVisibility) return undefined;

return this.props.columnVisibility
.filter(col => this.clinicalAttributeIds.has(col.id))
.map(col => ({
key: col.id,
label: col.name,
selected: col.visible,
}));
}

@computed get clinicalTabContent() {
return (
<div>
{this.props.columnVisibility && this.clinicalOptions && (
<AddChartByType
options={this.clinicalOptions}
freqPromise={
this.clinicalAttributeIdToAvailableFrequencyPromise
}
onAddAll={(ids: string[]) =>
this.addAll(ids, this.clinicalOptions)
}
onClearAll={(ids: string[]) =>
this.clearAll(ids, this.clinicalOptions)
}
onToggleOption={this.toggle}
optionsGivenInSortedOrder={false}
width={this.dropdownWidth}
/>
)}
</div>
);
}

@computed get mutationsTabText() {
return (
<div>
{Tab.MUTATIONS}
<span style={{ paddingLeft: 5 }}>
<span className="oncoprintDropdownCount">
{this.mutationsOptions?.length}
</span>
</span>
</div>
);
}

@computed get clinicalTabText() {
return (
<div>
{Tab.CLINICAL}
<span style={{ paddingLeft: 5 }}>
<span className="oncoprintDropdownCount">
{this.clinicalOptions?.length}
</span>
</span>
</div>
);
}

private getTextPixel(text: string | undefined, fontSize: string) {
// This is a very specified function to calculate the text length in Add Tracks dropdown
const FRONT_FAMILY = 'Helvetica Neue';
return Math.floor(
getTextWidth(text ? text : '', FRONT_FAMILY, fontSize)
);
}

@computed get dropdownWidth() {
let width = 2 * CONTAINER_PADDING_WIDTH;
const HEADER_FONT_SIZE = '14px';
const COUNT_FONT_SIZE = '11px';

const textWidth =
this.getTextPixel(Tab.CLINICAL, HEADER_FONT_SIZE) +
TAB_PADDING_WIDTH;
const countTextWidth =
this.getTextPixel(
this.clinicalOptions?.length.toString(),
COUNT_FONT_SIZE
) + COUNT_PADDING_WIDTH;
width += textWidth + countTextWidth;

return Math.max(width, MIN_DROPDOWN_WIDTH);
}

render() {
return (
<div style={{ float: 'right' }}>
<CustomDropdown
bsStyle="default"
title="Columns"
id="addColumnsDropdown"
className={this.props.className}
styles={{ minWidth: MIN_DROPDOWN_WIDTH, width: 'auto' }}
buttonClassName="btn btn-default btn-sm"
>
<div
style={{
display: 'flex',
flexDirection: 'column',
}}
>
<MSKTabs
activeTabId={this.tabId}
onTabClick={this.updateTabId}
unmountOnHide={false}
className="mainTabs mutationsTabAddColumnsDropdown"
>
<MSKTab
key={0}
id={Tab.MUTATIONS}
linkText={this.mutationsTabText}
>
{this.mutationsTabContent}
</MSKTab>
<MSKTab
key={1}
id={Tab.CLINICAL}
linkText={this.clinicalTabText}
>
{this.clinicalTabContent}
</MSKTab>
</MSKTabs>
</div>
</CustomDropdown>
</div>
);
}
}
12 changes: 11 additions & 1 deletion src/pages/resultsView/mutation/ResultsViewMutationMapper.tsx
Expand Up @@ -88,7 +88,10 @@ export default class ResultsViewMutationMapper extends MutationMapper<
this.props.store.mutationData,
this.props.store.indexedVariantAnnotations,
this.props.store.activeTranscript,
this.props.store.clinicalDataGroupedBySampleMap
this.props.store.clinicalDataGroupedBySampleMap,
this.props.store.mutationsTabClinicalAttributes,
this.props.store.clinicalAttributeIdToAvailableSampleCount,
this.props.store.sampleCount
) === 'pending'
);
}
Expand Down Expand Up @@ -164,6 +167,13 @@ export default class ResultsViewMutationMapper extends MutationMapper<
existsSomeMutationWithAscnProperty={
this.props.existsSomeMutationWithAscnProperty
}
mutationsTabClinicalAttributes={
this.props.store.mutationsTabClinicalAttributes
}
clinicalAttributeIdToAvailableSampleCount={
this.props.store.clinicalAttributeIdToAvailableSampleCount
}
sampleCount={this.props.store.sampleCount}
/>
);
}
Expand Down
Expand Up @@ -2,6 +2,7 @@ import { IHotspotIndex } from 'cbioportal-utils';
import {
Mutation,
Gene,
ClinicalAttribute,
ClinicalData,
CancerStudy,
MolecularProfile,
Expand Down Expand Up @@ -69,6 +70,11 @@ export default class ResultsViewMutationMapperStore extends MutationMapperStore
public clinicalDataGroupedBySampleMap: MobxPromise<{
[sampleId: string]: ClinicalData[];
}>,
public mutationsTabClinicalAttributes: MobxPromise<ClinicalAttribute[]>,
public clinicalAttributeIdToAvailableSampleCount: MobxPromise<{
[id: string]: number;
}>,
public sampleCount: MobxPromise<number>,
protected genomenexusClient?: GenomeNexusAPI,
protected genomenexusInternalClient?: GenomeNexusAPIInternal,
public getTranscriptId?: () => string
Expand Down

0 comments on commit 4b0b8a8

Please sign in to comment.