Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/pages/patientView/PatientViewPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,8 @@ export class PatientViewPageInner extends React.Component<
{patientViewTabs(
this,
this.urlWrapper,
this.pageStore.sampleManager.result!
this.pageStore.sampleManager.result!,
this.props.appStore
)}
</>
);
Expand Down
25 changes: 22 additions & 3 deletions src/pages/patientView/PatientViewPageTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ import { HelpWidget } from 'shared/components/HelpWidget/HelpWidget';
import MutationTableWrapper from './mutation/MutationTableWrapper';
import { PatientViewPageInner } from 'pages/patientView/PatientViewPage';
import { Else, If } from 'react-if';
import PatientReportTab from './oncokb/PatientReportTab';
import { AppStore } from 'AppStore';
import { FeatureFlagEnum } from 'shared/featureFlags';

export enum PatientViewPageTabs {
Summary = 'summary',
Expand All @@ -49,6 +52,7 @@ export enum PatientViewPageTabs {
TrialMatchTab = 'trialMatchTab',
MutationalSignatures = 'mutationalSignatures',
PathwayMapper = 'pathways',
OncoKBPatientReport = 'oncokbKBPatientReport',
}

export const PatientViewResourceTabPrefix = 'openResource_';
Expand All @@ -69,7 +73,8 @@ export function extractResourceIdFromTabId(tabId: string) {
export function patientViewTabs(
pageInstance: PatientViewPageInner,
urlWrapper: PatientViewUrlWrapper,
sampleManager: SampleManager | null
sampleManager: SampleManager | null,
appStore: AppStore
) {
return (
<MSKTabs
Expand All @@ -84,15 +89,15 @@ export function patientViewTabs(
<HelpWidget path={urlWrapper.routing.location.pathname} />
}
>
{tabs(pageInstance, sampleManager, urlWrapper)}
{tabs(pageInstance, sampleManager, appStore)}
</MSKTabs>
);
}

export function tabs(
pageComponent: PatientViewPageInner,
sampleManager: SampleManager | null,
urlWrapper: PatientViewUrlWrapper
appStore: AppStore
) {
const tabs: JSX.Element[] = [];
tabs.push(
Expand Down Expand Up @@ -702,6 +707,20 @@ export function tabs(
</MSKTab>
);

if (appStore.featureFlagStore.has(FeatureFlagEnum.ONCOKB_PATIENT_REPORT)) {
tabs.push(
<MSKTab
key={9}
id={PatientViewPageTabs.OncoKBPatientReport}
linkText="OncoKB Patient Report"
>
<PatientReportTab
patientViewPageStore={pageComponent.patientViewPageStore}
/>
</MSKTab>
);
}

pageComponent.resourceTabs.component &&
/* @ts-ignore */
tabs.push(...pageComponent.resourceTabs.component);
Expand Down
90 changes: 90 additions & 0 deletions src/pages/patientView/oncokb/PatientReportTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { observer } from 'mobx-react';
import React, { useEffect } from 'react';
import IFrameLoader from 'shared/components/iframeLoader/IFrameLoader';
import WindowStore from 'shared/components/window/WindowStore';
import { CLINICAL_ATTRIBUTE_ID_ENUM } from 'shared/constants';
import { PatientViewPageStore } from '../clinicalInformation/PatientViewPageStore';
import { getAnnotatedSamples } from './PatientReportUtils';

interface IPatientReportTabProps {
patientViewPageStore: PatientViewPageStore;
}

class PatientMetadata {
id = '';
name = '';
age = '';
sex = '';
}

const IFRAME_NAME = 'patient-report-iframe';

const PatientReportTab = observer(
({ patientViewPageStore }: IPatientReportTabProps) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should type this using React.FunctionComponent generic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we want to do this since React does not recommend. It's a rather trivial change so I'm find with it if it helps maintain the style of the codebase, but wondering your thoughts on this: facebook/create-react-app#8177

const isDataComplete =
patientViewPageStore.clinicalDataGroupedBySample.isComplete &&
patientViewPageStore.patientViewData.isComplete &&
patientViewPageStore.mutationData.isComplete &&
patientViewPageStore.oncoKbData.isComplete &&
patientViewPageStore.discreteCNAData.isComplete &&
patientViewPageStore.cnaOncoKbData.isComplete &&
patientViewPageStore.structuralVariantData.isComplete &&
patientViewPageStore.structuralVariantOncoKbData.isComplete;

useEffect(() => {
if (isDataComplete) {
const patientMetadata = new PatientMetadata();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets put this code in a utitility file somewhere and add some comments about what it does.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i mean the whole contents of the this useEffect hook. be careful though to dereference data from the mobx promises before you pass them into this function

patientMetadata.id = patientViewPageStore.patientId;
patientMetadata.sex =
patientViewPageStore.patientViewData.result.patient?.clinicalData.find(
data =>
data.clinicalAttributeId ===
CLINICAL_ATTRIBUTE_ID_ENUM.SEX
)?.value ?? '';

const samples = getAnnotatedSamples(
patientViewPageStore.clinicalDataGroupedBySample.result,
patientViewPageStore.uniqueSampleKeyToTumorType,
patientViewPageStore.mutationData.result,
patientViewPageStore.oncoKbData.result,
patientViewPageStore.discreteCNAData.result,
patientViewPageStore.cnaOncoKbData.result,
patientViewPageStore.structuralVariantData.result,
patientViewPageStore.structuralVariantOncoKbData.result
);

const form = document.createElement('form');
form.method = 'POST';
form.action =
'https://beta.report.oncokb.org/report/cbioportal';
form.target = IFRAME_NAME;

let input = document.createElement('input');
input.type = 'hidden';
input.name = 'annotatedSamples';
input.value = JSON.stringify(samples);
form.appendChild(input);

input = document.createElement('input');
input.type = 'hidden';
input.name = 'patientMetadata';
input.value = JSON.stringify(patientMetadata);
form.appendChild(input);

document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}
}, [isDataComplete]);

return (
<div>
<IFrameLoader
height={WindowStore.size.height - 220}
name={IFRAME_NAME}
/>
</div>
);
}
);
export default PatientReportTab;
101 changes: 101 additions & 0 deletions src/pages/patientView/oncokb/PatientReportUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { IOncoKbData } from 'cbioportal-utils';
import { getAnnotatedSamples } from './PatientReportUtils';
import {
ClinicalData,
DiscreteCopyNumberData,
Mutation,
StructuralVariant,
} from 'cbioportal-ts-api-client';
import { assert } from 'chai';

describe('Patient Report Utils', () => {
const clinicalDataGroupedBySample: {
id: string;
clinicalData: ClinicalData[];
}[] = [
{ id: '1', clinicalData: [] },
{ id: '2', clinicalData: [] },
];
const uniqueSampleKeyToTumorType: { [sampleId: string]: string } = {
'1': 'Melanoma',
'2': 'Breast Cancer',
};
const mutations: Partial<Mutation>[] = [
{
entrezGeneId: 673,
gene: {
entrezGeneId: 673,
geneticEntityId: 0,
hugoGeneSymbol: 'BRAF',
type: '',
},
mutationType: 'Missense_Mutation',
proteinChange: 'V600E',
sampleId: '1',
uniqueSampleKey: '1',
},
];
const mutationsOncoKBData: IOncoKbData = {
indicatorMap: { '673_Melanoma_V600E_Missense_Mutation': {} as any },
};
const copyNumberAlterations: Partial<DiscreteCopyNumberData>[] = [
{
alteration: 2, // Amplification
entrezGeneId: 2064,
gene: {
entrezGeneId: 2064,
geneticEntityId: 0,
hugoGeneSymbol: 'ERBB2',
type: '',
},
molecularProfileId: '',
sampleId: '1',
uniqueSampleKey: '1',
},
];
const copyNumberAlterationsOncoKBData: IOncoKbData = {
indicatorMap: { '2064_Melanoma_Amplification': {} as any },
};
const structuralVariants: Partial<StructuralVariant>[] = [
{
sampleId: '2',
site1EntrezGeneId: 613,
site1HugoSymbol: 'BCR',
site2EntrezGeneId: 25,
site2HugoSymbol: 'ABL1',
uniqueSampleKey: '2',
},
];
const structuralVariantsOncoKBData: IOncoKbData = { indicatorMap: {} };

it('should construct samples from alteration data', () => {
const annotatedSamples = getAnnotatedSamples(
clinicalDataGroupedBySample,
uniqueSampleKeyToTumorType,
mutations as Mutation[],
mutationsOncoKBData,
copyNumberAlterations as DiscreteCopyNumberData[],
copyNumberAlterationsOncoKBData,
structuralVariants as StructuralVariant[],
structuralVariantsOncoKBData
);

const sample1 = annotatedSamples.find(sample => sample.id === '1');
assert(sample1 !== undefined);
const sample2 = annotatedSamples.find(sample => sample.id === '2');
assert(sample2 !== undefined);

assert(sample1!.mutations.length === 1);
assert(sample1!.copyNumberAlterations.length === 1);
assert(sample1!.structuralVariants.length === 0);

assert(sample2!.mutations.length === 0);
assert(sample2!.copyNumberAlterations.length === 0);
assert(sample2!.structuralVariants.length === 1);

const structuralVariant = sample2!.structuralVariants[0];
assert(structuralVariant.query.hugoSymbol === 'BCR');
assert(structuralVariant.query.alteration === 'BCR-ABL1 Fusion');
assert(structuralVariant.query.consequence === 'fusion');
});
});
Loading