-
- Mutation Assessor
- {' '}
- predicts the functional impact of amino-acid substitutions in
- proteins, such as mutations discovered in cancer or missense
- polymorphisms. The functional impact is assessed based on
- evolutionary conservation of the affected amino acid in protein
- homologs. The method has been validated on a large set (60k) of
- disease associated (OMIM) and polymorphic variants.
+
+ Mutation Assessor predicts the functional impact of amino-acid
+ substitutions in proteins, such as mutations discovered in
+ cancer or missense polymorphisms. The functional impact is
+ assessed based on evolutionary conservation of the affected
+ amino acid in protein homologs. The method has been validated on
+ a large set of disease associated and polymorphic variants (
+
+ ClinVar
+
+ ).
+
+
+ Mutation Assessor V4 data is available in the portal since
+ Oct. 8, 2024.
+ {' '}
+ New manuscript is in progress. Click{` `}
+
+ here
+
+ {` `} to see information about V3 data.
);
}
@@ -143,24 +151,6 @@ export default class MutationAssessor extends React.Component<
);
}
- // This is mostly to make the legacy MA links work
- private static maLink(link: string | undefined) {
- let url = null;
-
- // ignore invalid links ("", "NA", "Not Available")
- if (link) {
- // getma.org is the legacy link, need to replace it with the actual value
- url = link.replace('getma.org', 'mutationassessor.org/r3');
-
- // prepend "http://" if needed
- if (url.indexOf('http://') !== 0) {
- url = `http://${url}`;
- }
- }
-
- return url;
- }
-
constructor(props: IMutationAssessorProps) {
super(props);
@@ -169,21 +159,13 @@ export default class MutationAssessor extends React.Component<
public render() {
let maContent: JSX.Element =
;
- const dataSource = (
- <>
- Mutation Assessor
-
- {!this.props.isCanonicalTranscriptSelected &&
* }
- >
- );
-
if (
this.props.mutationAssessor &&
- this.props.mutationAssessor.functionalImpact != null &&
- this.props.mutationAssessor.functionalImpact !== ''
+ this.props.mutationAssessor.functionalImpactPrediction != null &&
+ this.props.mutationAssessor.functionalImpactPrediction !== ''
) {
const maData = this.props.mutationAssessor;
- maContent =
{maData.functionalImpact} ;
+ maContent =
{maData.functionalImpactPrediction} ;
} else {
maContent =
N/A ;
}
@@ -192,25 +174,19 @@ export default class MutationAssessor extends React.Component<
{this.mutationAssessorTooltip(
-
- {dataSource}
-
+ <>
+ Mutation Assessor
+
+ {!this.props.isCanonicalTranscriptSelected && (
+
*
+ )}
+ >
)}
@@ -221,12 +197,7 @@ export default class MutationAssessor extends React.Component<
private mutationAssessorData() {
if (this.props.mutationAssessor) {
const maData = this.props.mutationAssessor;
- const xVarLink = MutationAssessor.maLink(
- `http://mutationassessor.org/r3/?cm=var&p=${maData.uniprotId}&var=${maData.variant}`
- );
- const msaLink = MutationAssessor.maLink(maData.msaLink);
- const pdbLink = MutationAssessor.maLink(maData.pdbLink);
- const impact = maData.functionalImpact ? (
+ const impact = maData.functionalImpactPrediction ? (
{(maData.functionalImpactScore ||
@@ -245,86 +216,12 @@ export default class MutationAssessor extends React.Component<
)}
-
- Please refer to the score range{' '}
-
- here
-
- .
-
) : null;
- const xVar =
- xVarLink &&
- maData.uniprotId.length !== 0 &&
- maData.variant.length !== 0 ? (
-
- ) : null;
-
- const msa =
- msaLink && maData.msaLink.length !== 0 ? (
-
- ) : null;
-
- const pdb =
- pdbLink && maData.pdbLink.length !== 0 ? (
-
- ) : null;
-
return (
{impact}
- {msa}
- {pdb}
- {xVar}
);
diff --git a/src/appBootstrapper.tsx b/src/appBootstrapper.tsx
index d36088736a4..33dc126fd0a 100755
--- a/src/appBootstrapper.tsx
+++ b/src/appBootstrapper.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
-import { configure } from 'mobx';
+import { configure, toJS } from 'mobx';
import { Provider } from 'mobx-react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
@@ -27,7 +27,12 @@ import browser from 'bowser';
import { setNetworkListener } from './shared/lib/ajaxQuiet';
import { initializeTracking, sendToLoggly } from 'shared/lib/tracking';
import superagentCache from 'superagent-cache';
-import { getBrowserWindow, onMobxPromise } from 'cbioportal-frontend-commons';
+import {
+ getBrowserWindow,
+ hashString,
+ isWebdriver,
+ onMobxPromise,
+} from 'cbioportal-frontend-commons';
import { AppStore } from './AppStore';
import { handleLongUrls } from 'shared/lib/handleLongUrls';
import 'shared/polyfill/canvasToBlob';
@@ -39,6 +44,7 @@ import { FeatureFlagStore } from 'shared/FeatureFlagStore';
import eventBus from 'shared/events/eventBus';
import { SiteError } from 'shared/model/appMisc';
import load from 'little-loader';
+import internalClient from 'shared/api/cbioportalInternalClientInstance';
export interface ICBioWindow {
globalStores: {
@@ -206,6 +212,69 @@ superagent.Request.prototype.end = function(callback) {
}
});
};
+
+function enableDataDogTracking(store: AppStore) {
+ datadogLogs.init({
+ clientToken: 'pub9a94ebb002f105ff44d8e427b6549775',
+ site: 'datadoghq.com',
+ service: 'cbioportalinternal',
+ forwardErrorsToLogs: true,
+ sessionSampleRate: 100,
+ } as any);
+
+ const match = [
+ /filtered-samples/,
+ /clinical-data-bin-counts/,
+ /generic-assay-data-bin-counts/,
+ /mutated-genes/,
+ /molecular-profile-sample-counts/,
+ /cna-genes/,
+ /structuralvariant-genes/,
+ /clinical-data-counts/,
+ /sample-lists-counts/,
+ /clinical-data-density-plot/,
+ /clinical-data-violin-plots/,
+ /genomic-data-counts/,
+ /mutation-data-counts/,
+ /clinical-event-type-counts/,
+ /treatments\/patient-counts/,
+ /treatments\/sample-counts/,
+ /genomic-data-bin-counts/,
+ /clinical-event-type-counts/,
+ ];
+
+ const oldRequest = (internalClient as any).request;
+ (internalClient as any).request = function(...args: any) {
+ try {
+ let url = args[1];
+
+ if (Object.keys(args[4]).length) {
+ url = url + '?' + $.param(args[4]);
+ }
+
+ const data = args[2];
+
+ const studyIds = data.studyIds || data.studyViewFilter.studyIds;
+
+ const appName = store.serverConfig.app_name;
+
+ if (studyIds.length < 4 && _.some(match, re => re.test(url))) {
+ const hash = hashString(url + JSON.stringify(toJS(data)));
+ datadogLogs.logger.info('study view request', {
+ url,
+ data,
+ hash,
+ appName,
+ });
+ }
+ } catch (ex) {
+ // fail silently
+ }
+
+ return oldRequest.apply(this, args);
+ };
+}
+
//
browserWindow.routingStore = routingStore;
@@ -236,24 +305,6 @@ let render = (key?: number) => {
]`;
}
- if (stores.appStore.serverConfig.app_name === 'mskcc-portal') {
- datadogLogs.init({
- clientToken: 'pub9a94ebb002f105ff44d8e427b6549775',
- site: 'datadoghq.com',
- service: 'cbioportalinternal',
- forwardErrorsToLogs: true,
- sessionSampleRate: 100,
- beforeSend: (log: any) => {
- switch (log.origin) {
- case 'console':
- return false;
- default:
- // let dd send log
- }
- },
- } as any);
- }
-
const rootNode = document.getElementById('reactRoot');
ReactDOM.render(
@@ -319,6 +370,15 @@ $(document).ready(async () => {
initializeAppStore(stores.appStore);
+ if (
+ ['genie-public-portal', 'public-portal'].includes(
+ stores.appStore.serverConfig.app_name!
+ ) &&
+ !isWebdriver()
+ ) {
+ enableDataDogTracking(stores.appStore);
+ }
+
await loadCustomJs();
render();
diff --git a/src/appShell/App/usageAgreements/StudyAgreement.tsx b/src/appShell/App/usageAgreements/StudyAgreement.tsx
index f430ba59921..75ba1fcc0ec 100644
--- a/src/appShell/App/usageAgreements/StudyAgreement.tsx
+++ b/src/appShell/App/usageAgreements/StudyAgreement.tsx
@@ -28,11 +28,21 @@ export const StudyAgreement: React.FunctionComponent<{}> = function({}) {
- WARNING:
- All URLs in this website are private - do NOT include
- in manuscripts.
+ Attention:
+ Please read and follow the{' '}
+
+ rules about usage of MSK clinical sequencing data in
+ manuscripts
+
+ .
>
}
+ dismissButtonText={'Acknowledge'}
persistenceKey={STUDY_VIEW_WARNING_PERSISTENCE_KEY}
expirationInDays={90}
clauses={[
@@ -56,31 +66,13 @@ export const StudyAgreement: React.FunctionComponent<{}> = function({}) {
public cBioPortal.
>,
<>
- When adding a link to a cBioPortal cohort in a manuscript,{' '}
-
- I will not link to this private portal (
- {window.location.hostname})
-
- , but will instead link to this study on the public
- cBioPortal (
-
- cbioportal.org
-
- ). Contact{' '}
-
- cbioportal@cbio.mskcc.org
- {' '}
- with any questions about getting the data transferred to the
- public cBioPortal.
- >,
- <>
- I have read the{' '}
+ I have read and agree to the{' '}
- MSK-IMPACT Data publication guidelines (intranet/VPN
- only)
+ MSK-IMPACT Memorial Hospital Research Data Governance
+ publication guidelines
.
>,
diff --git a/src/globalStyles/global.scss b/src/globalStyles/global.scss
index 81f33b14a2d..7b1839b6d60 100755
--- a/src/globalStyles/global.scss
+++ b/src/globalStyles/global.scss
@@ -47,6 +47,14 @@ div:active {
}
}
+.form-group-inline {
+ @extend .form-group;
+
+ label {
+ margin-right: 6px;
+ }
+}
+
.posRelative {
position: relative;
}
diff --git a/src/pages/patientView/patientHeader/SignificantMutationalSignatures.tsx b/src/pages/patientView/patientHeader/SignificantMutationalSignatures.tsx
index c62f011fc15..da59e06fd5b 100644
--- a/src/pages/patientView/patientHeader/SignificantMutationalSignatures.tsx
+++ b/src/pages/patientView/patientHeader/SignificantMutationalSignatures.tsx
@@ -74,11 +74,13 @@ export default class SignificantMutationalSignatures extends React.Component<
)}
{_.map(dataToShow, significantSignature => (
))}
@@ -89,13 +91,14 @@ export default class SignificantMutationalSignatures extends React.Component<
{dataToShow.map(significantSignature => (
- {significantSignature.meta.name}
+ {significantSignature.meta?.name || 'NA'}
@@ -113,7 +116,7 @@ export default class SignificantMutationalSignatures extends React.Component<
significantMutationalSignaturesForSample,
(acc, significantSignature) => {
const color = getColorByMutationalSignatureCategory(
- significantSignature.meta.category
+ significantSignature.meta?.category
);
if (color in acc) {
acc[color] += significantSignature.value;
diff --git a/src/pages/resultsView/ResultsViewPage.tsx b/src/pages/resultsView/ResultsViewPage.tsx
index 09423eeade7..62a0868d13a 100644
--- a/src/pages/resultsView/ResultsViewPage.tsx
+++ b/src/pages/resultsView/ResultsViewPage.tsx
@@ -265,7 +265,10 @@ export default class ResultsViewPage extends React.Component<
entrezGeneIdToGene={store.entrezGeneIdToGene}
sampleKeyToSample={store.sampleKeyToSample}
genes={store.genes}
- clinicalAttributes={store.clinicalAttributes}
+ clinicalAttributes={
+ store.plotClinicalAttributes
+ }
+ customAttributes={store.customAttributes}
genesets={store.genesets}
genericAssayEntitiesGroupByMolecularProfileId={
store.genericAssayEntitiesGroupByMolecularProfileId
diff --git a/src/pages/resultsView/ResultsViewPageStore.ts b/src/pages/resultsView/ResultsViewPageStore.ts
index ea50031be8e..adef8acb186 100644
--- a/src/pages/resultsView/ResultsViewPageStore.ts
+++ b/src/pages/resultsView/ResultsViewPageStore.ts
@@ -1093,7 +1093,7 @@ export class ResultsViewPageStore extends AnalysisStore
this.studyIds,
this.clinicalAttributes_profiledIn,
this.clinicalAttributes_comparisonGroupMembership,
- this.clinicalAttributes_customCharts,
+ this.customAttributes,
this.samples,
this.patients,
],
@@ -1142,7 +1142,7 @@ export class ResultsViewPageStore extends AnalysisStore
...specialAttributes,
...this.clinicalAttributes_profiledIn.result!,
...this.clinicalAttributes_comparisonGroupMembership.result!,
- ...this.clinicalAttributes_customCharts.result!,
+ ...this.customAttributes.result!,
];
},
});
@@ -1187,7 +1187,7 @@ export class ResultsViewPageStore extends AnalysisStore
this.studyToDataQueryFilter,
this.clinicalAttributes_profiledIn,
this.clinicalAttributes_comparisonGroupMembership,
- this.clinicalAttributes_customCharts,
+ this.customAttributes,
],
invoke: async () => {
let clinicalAttributeCountFilter: ClinicalAttributeCountFilter;
@@ -1270,7 +1270,7 @@ export class ResultsViewPageStore extends AnalysisStore
);
}
// add counts for custom chart clinical attributes
- for (const attr of this.clinicalAttributes_customCharts.result!) {
+ for (const attr of this.customAttributes.result!) {
ret[attr.clinicalAttributeId] = attr.data!.filter(
d => d.value !== 'NA'
).length;
@@ -2723,7 +2723,17 @@ export class ResultsViewPageStore extends AnalysisStore
default: [],
});
- readonly clinicalAttributes_customCharts = remoteData({
+ readonly plotClinicalAttributes = remoteData({
+ await: () => [this.clinicalAttributes, this.customAttributes],
+ invoke: async () => {
+ return _.filter(
+ this.clinicalAttributes.result!,
+ attr => !this.customAttributes.result!.includes(attr)
+ );
+ },
+ });
+
+ readonly customAttributes = remoteData({
await: () => [this.sampleMap],
invoke: async () => {
let ret: ExtendedClinicalAttribute[] = [];
@@ -5693,7 +5703,7 @@ export class ResultsViewPageStore extends AnalysisStore
this.coverageInformation,
this.filteredSampleKeyToSample,
this.filteredPatientKeyToPatient,
- this.clinicalAttributes_customCharts
+ this.customAttributes
);
public mutationCache = new MobxPromiseCache<
diff --git a/src/pages/resultsView/mutation/Mutations.tsx b/src/pages/resultsView/mutation/Mutations.tsx
index 9c098d005ff..24866e63a36 100644
--- a/src/pages/resultsView/mutation/Mutations.tsx
+++ b/src/pages/resultsView/mutation/Mutations.tsx
@@ -334,14 +334,20 @@ export default class Mutations extends React.Component<
count: number,
mutations: Mutation[],
axisMode: AxisScale
- ): JSX.Element {
- return (
-
- );
+ ) {
+ if (this.props.store.filteredPatients.isComplete) {
+ return (
+
+ );
+ } else {
+ return <>>;
+ }
}
}
diff --git a/src/pages/resultsView/oncoprint/TracksMenu.tsx b/src/pages/resultsView/oncoprint/TracksMenu.tsx
index 839eb7aa698..a8910a227ba 100644
--- a/src/pages/resultsView/oncoprint/TracksMenu.tsx
+++ b/src/pages/resultsView/oncoprint/TracksMenu.tsx
@@ -151,7 +151,7 @@ export default class TracksMenu extends React.Component {
await: () => [
this.props.store.clinicalAttributes,
this.clinicalAttributeIdToAvailableFrequency,
- this.props.store.clinicalAttributes_customCharts,
+ this.props.store.customAttributes,
],
invoke: () => {
const uniqueAttributes = _.uniqBy(
@@ -167,7 +167,7 @@ export default class TracksMenu extends React.Component {
};
const customChartClinicalAttributeIds = _.keyBy(
- this.props.store.clinicalAttributes_customCharts.result!,
+ this.props.store.customAttributes.result!,
a => a.clinicalAttributeId
);
diff --git a/src/pages/studyView/StudyViewPage.tsx b/src/pages/studyView/StudyViewPage.tsx
index 0971f3c73fe..5628f49ba2d 100644
--- a/src/pages/studyView/StudyViewPage.tsx
+++ b/src/pages/studyView/StudyViewPage.tsx
@@ -769,6 +769,9 @@ export default class StudyViewPage extends React.Component<
clinicalAttributes={
this.store.clinicalAttributes
}
+ customAttributes={
+ this.store.customAttributes
+ }
genesets={this.store.genesets}
genericAssayEntitiesGroupByMolecularProfileId={
this.store
diff --git a/src/pages/studyView/StudyViewPageStore.ts b/src/pages/studyView/StudyViewPageStore.ts
index cafc79680de..faf5c3cc105 100644
--- a/src/pages/studyView/StudyViewPageStore.ts
+++ b/src/pages/studyView/StudyViewPageStore.ts
@@ -11477,7 +11477,7 @@ export class StudyViewPageStore
),
});
- readonly clinicalAttributes_customCharts = remoteData({
+ readonly customAttributes = remoteData({
await: () => [this.sampleMap],
invoke: async () => {
let ret: ExtendedClinicalAttribute[] = [];
@@ -11523,7 +11523,7 @@ export class StudyViewPageStore
this.coverageInformation,
this.filteredSampleKeyToSample,
this.filteredPatientKeyToPatient,
- this.clinicalAttributes_customCharts
+ this.customAttributes
);
private _numericGeneMolecularDataCache = new MobxPromiseCache<
diff --git a/src/pages/studyView/StudyViewUtils.tsx b/src/pages/studyView/StudyViewUtils.tsx
index e2e8b78bc78..06c56f02131 100644
--- a/src/pages/studyView/StudyViewUtils.tsx
+++ b/src/pages/studyView/StudyViewUtils.tsx
@@ -903,7 +903,8 @@ export function getVirtualStudyDescription(
attributeNamesSet: { [id: string]: string },
molecularProfileNameSet: { [id: string]: string },
caseListNameSet: { [key: string]: string },
- user?: string
+ user?: string,
+ hideSampleCounts: boolean = false
) {
let descriptionLines: string[] = [];
const createdOnStr = 'Created on';
@@ -917,18 +918,24 @@ export function getVirtualStudyDescription(
_.flatMap(studyWithSamples, study => study.uniqueSampleKeys)
);
descriptionLines.push(
- `${uniqueSampleKeys.length} sample${
- uniqueSampleKeys.length > 1 ? 's' : ''
- } from ${studyWithSamples.length} ${
- studyWithSamples.length > 1 ? 'studies:' : 'study:'
- }`
+ (hideSampleCounts
+ ? 'Samples'
+ : `${uniqueSampleKeys.length} sample${
+ uniqueSampleKeys.length > 1 ? 's' : ''
+ }`) +
+ ` from ${studyWithSamples.length} ${
+ studyWithSamples.length > 1 ? 'studies:' : 'study:'
+ }`
);
//add individual studies sample count
studyWithSamples.forEach(studyObj => {
descriptionLines.push(
- `- ${studyObj.name} (${
- studyObj.uniqueSampleKeys.length
- } sample${uniqueSampleKeys.length > 1 ? 's' : ''})`
+ `- ${studyObj.name}` +
+ (hideSampleCounts
+ ? ''
+ : ` (${studyObj.uniqueSampleKeys.length} sample${
+ uniqueSampleKeys.length > 1 ? 's' : ''
+ })`)
);
});
//add filters
diff --git a/src/pages/studyView/addChartButton/AddChartButton.tsx b/src/pages/studyView/addChartButton/AddChartButton.tsx
index 8246b346ba2..0955e19b9fd 100644
--- a/src/pages/studyView/addChartButton/AddChartButton.tsx
+++ b/src/pages/studyView/addChartButton/AddChartButton.tsx
@@ -19,7 +19,11 @@ import { StudyViewPageTabKeyEnum } from 'pages/studyView/StudyViewPageTabs';
import autobind from 'autobind-decorator';
import _ from 'lodash';
import AddChartByType from './addChartByType/AddChartByType';
-import { DefaultTooltip, remoteData } from 'cbioportal-frontend-commons';
+import {
+ DefaultTooltip,
+ isWebdriver,
+ remoteData,
+} from 'cbioportal-frontend-commons';
import CustomCaseSelection from './customCaseSelection/CustomCaseSelection';
import {
calculateClinicalDataCountFrequency,
@@ -50,6 +54,7 @@ import { CustomChartData } from 'shared/api/session-service/sessionServiceModels
import ReactSelect from 'react-select';
import { GenericAssayMeta } from 'cbioportal-ts-api-client';
import { DataTypeConstants } from 'shared/constants';
+import { Else, If, Then } from 'react-if';
export interface IAddChartTabsProps {
store: StudyViewPageStore;
@@ -629,6 +634,23 @@ class AddChartTabs extends React.Component {
@computed get notificationMessages() {
let notificationMessages: JSX.Element[] = [];
+ if (!this.props.store.isLoggedIn) {
+ notificationMessages.push(
+ <>
+
+ openSocialAuthWindow(this.props.store.appStore)
+ }
+ >
+ Login
+
+ to add custom data charts to your profile.
+ >
+ );
+ return notificationMessages;
+ }
+
if (this.props.store.customChartSet.size > 0) {
// Notify if there any shared custom data
if (this.existSharedCustomData) {
@@ -644,28 +666,6 @@ class AddChartTabs extends React.Component {
are shared custom data.
>
);
- }
- // Notify that shared and page-session custom data are not saved for non-logged users
- if (
- !this.props.store.isLoggedIn &&
- this.props.store.appStore.isSocialAuthenticated
- ) {
- if (notificationMessages.length > 0) {
- notificationMessages.push( );
- }
- notificationMessages.push(
- <>
-
- openSocialAuthWindow(this.props.store.appStore)
- }
- >
- Login
-
- to save custom data charts to your profile.
- >
- );
} else if (this.existSharedCustomData) {
// Notify if shared custom data are saved to user profile
if (notificationMessages.length > 0) {
@@ -911,104 +911,135 @@ class AddChartTabs extends React.Component {
this.getTabsWidth - CONTAINER_PADDING_WIDTH,
}}
>
- {this.customChartDataOptions.length > 0 && (
-
- {this.isAddNewChartWindowVisible
- ? 'Cancel'
- : '+ Add new custom data'}
-
+ {!this.props.store.isLoggedIn && (
+ <>
+
+
+ Please {this.notificationMessages}
+
+ >
)}
-
- {this.isAddNewChartWindowVisible && (
- {
- this.showAddNewChart = false;
- this.savingCustomData = true;
- this.updateInfoMessage(
- `Saving ${chart.displayName}`
- );
- this.props.store
- .addCustomChart(chart)
- .then(() => {
- this.savingCustomData = false;
- this.updateInfoMessage(
- `${chart.displayName} has been added.`
- );
- });
- }}
- />
- )}
-
- <>
- {this.customChartDataOptions.length > 0 && (
- <>
-
-
+ {this.customChartDataOptions.length > 0 && (
+
+ {this.isAddNewChartWindowVisible
+ ? 'Cancel'
+ : '+ Add new custom data'}
+
+ )}
+
+ {this.isAddNewChartWindowVisible && (
+ {
- this.props.store.toggleCustomChartMarkedForDeletion(
- id
- );
- }}
- restoreChart={(id: string) => {
- this.props.store.toggleCustomChartMarkedForDeletion(
- id
- );
- }}
- markedForDeletion={
+ submitButtonText={'Add Chart'}
+ queriedStudies={
this.props.store
- .customChartGroupMarkedForDeletion
+ .queriedPhysicalStudyIds
+ .result
}
+ isChartNameValid={
+ this.props.store
+ .isChartNameValid
+ }
+ getDefaultChartName={
+ this.props.store
+ .getDefaultCustomChartName
+ }
+ disableSubmitButton={
+ this.savingCustomData
+ }
+ onSubmit={(
+ chart: CustomChartData
+ ) => {
+ this.showAddNewChart = false;
+ this.savingCustomData = true;
+ this.updateInfoMessage(
+ `Saving ${chart.displayName}`
+ );
+ this.props.store
+ .addCustomChart(chart)
+ .then(() => {
+ this.savingCustomData = false;
+ this.updateInfoMessage(
+ `${chart.displayName} has been added.`
+ );
+ });
+ }}
/>
- >
- )}
- {this.notificationMessages.length > 0 && (
+ )}
+
<>
-
-
- Note: {this.notificationMessages}
-
+ {this.customChartDataOptions.length >
+ 0 && (
+ <>
+
+ {
+ this.props.store.toggleCustomChartMarkedForDeletion(
+ id
+ );
+ }}
+ restoreChart={(
+ id: string
+ ) => {
+ this.props.store.toggleCustomChartMarkedForDeletion(
+ id
+ );
+ }}
+ markedForDeletion={
+ this.props.store
+ .customChartGroupMarkedForDeletion
+ }
+ />
+ >
+ )}
+ {this.notificationMessages.length >
+ 0 && (
+ <>
+
+
+ Note:{' '}
+ {this.notificationMessages}
+
+ >
+ )}
>
- )}
- >
+
+ )}
+ internalClient.getAllStudyResourceDataInStudyPatientSampleUsingGET({
+ studyId: studyId,
+ projection: 'DETAILED',
+ })
+ );
+
+ return Promise.all(allResources).then(allResources =>
+ _(allResources)
+ .flatMap()
+ .value()
+ );
+}
+
function getResourceDataOfPatients(studyClinicalData: {
[uniqueSampleKey: string]: ClinicalData[];
}) {
@@ -86,6 +102,7 @@ function buildItemsAndResources(resourceData: {
typeOfResource: resource?.resourceDefinition?.displayName,
description: resource?.resourceDefinition?.description,
url: resource?.url,
+ resourceId: resource?.resourceId,
}))
)
.value();
@@ -95,28 +112,35 @@ function buildItemsAndResources(resourceData: {
async function fetchFilesLinksData(
filters: StudyViewFilter,
- sampleIdResourceData: { [sampleId: string]: ResourceData[] },
+ selectedSamples: Array,
searchTerm: string | undefined,
sortAttributeId: string | undefined,
sortDirection: 'asc' | 'desc' | undefined,
recordLimit: number
) {
- const studyClinicalDataResponse = await getAllClinicalDataByStudyViewFilter(
- filters,
- searchTerm,
- sortAttributeId,
- sortDirection,
- recordLimit,
- 0
- );
+ const selectedStudyIds = [
+ ...new Set(selectedSamples.map(item => item.studyId)),
+ ];
+
+ // sampleIds (+patientIds) for the selectedSamples
+ const selectedIds = new Map([
+ ...selectedSamples.map(item => [item.sampleId, item.studyId] as const),
+ ...selectedSamples.map(item => [item.patientId, item.studyId] as const),
+ ]);
- const resourcesForPatients = await getResourceDataOfPatients(
- studyClinicalDataResponse.data
+ // Fetch resources for entire study
+ const resourcesForEntireStudy = await getResourceDataOfEntireStudy(
+ selectedStudyIds
);
- const resourcesForPatientsAndSamples: { [key: string]: ResourceData[] } = {
- ...sampleIdResourceData,
- ...resourcesForPatients,
- };
+
+ // Filter the resources to consist of only studyView selected samples
+ // Also keep patient level resources (e.g. Those don't have a sampleId)
+ const resourcesForPatientsAndSamples = _(resourcesForEntireStudy)
+ .filter(resource =>
+ selectedIds.has(resource.sampleId || resource.patientId)
+ )
+ .groupBy(r => r.patientId)
+ .value();
// we create objects with the necessary properties for each resource
// calculate the total number of resources per patient.
@@ -195,7 +219,6 @@ export class FilesAndLinks extends React.Component {
await: () => [
this.props.store.selectedSamples,
this.props.store.resourceDefinitions,
- this.props.store.sampleResourceData,
],
onError: () => {},
invoke: async () => {
@@ -205,12 +228,13 @@ export class FilesAndLinks extends React.Component {
const resources = await fetchFilesLinksData(
this.props.store.filters,
- this.props.store.sampleResourceData.result!,
+ this.props.store.selectedSamples.result,
this.searchTerm,
'patientId',
'asc',
RECORD_LIMIT
);
+
return Promise.resolve(resources);
},
});
@@ -259,18 +283,41 @@ export class FilesAndLinks extends React.Component {
'Type Of Resource'
),
render: (data: { [id: string]: string }) => {
+ const path = `patient/openResource_${data.resourceId}`;
return (
+ );
+ },
+ },
+
+ {
+ ...this.getDefaultColumnConfig('resourceUrl', ''),
+ render: (data: { [id: string]: string }) => {
+ return (
+
);
@@ -287,7 +334,7 @@ export class FilesAndLinks extends React.Component {
{
...this.getDefaultColumnConfig(
'resourcesPerPatient',
- 'Number of Resource Per Patient',
+ 'Resources per Patient',
true
),
render: (data: { [id: string]: number }) => {
diff --git a/src/pages/studyView/virtualStudy/VirtualStudy.tsx b/src/pages/studyView/virtualStudy/VirtualStudy.tsx
index 4a492d847c4..f55a3db3404 100644
--- a/src/pages/studyView/virtualStudy/VirtualStudy.tsx
+++ b/src/pages/studyView/virtualStudy/VirtualStudy.tsx
@@ -97,7 +97,8 @@ export default class VirtualStudy extends React.Component<
{}
> {
@observable.ref private name: string;
- @observable.ref private description: string;
+ @observable.ref private customDescription: string | undefined;
+ @observable.ref private dynamic: boolean = false;
@observable private saving = false;
@observable private sharing = false;
@@ -107,17 +108,6 @@ export default class VirtualStudy extends React.Component<
super(props);
makeObservable(this);
this.name = props.name || '';
- this.description =
- props.description ||
- getVirtualStudyDescription(
- this.props.description,
- this.props.studyWithSamples,
- this.props.filter,
- this.attributeNamesSet,
- this.props.molecularProfileNameSet,
- this.props.caseListNameSet,
- this.props.user
- );
}
@computed get namePlaceHolder() {
@@ -167,6 +157,7 @@ export default class VirtualStudy extends React.Component<
study => study.studyId
),
studies: studies,
+ dynamic: this.dynamic,
};
return await sessionServiceClient.saveVirtualStudy(
parameters,
@@ -231,6 +222,30 @@ export default class VirtualStudy extends React.Component<
);
}
+ getDefuaultDescriptionByType(dynamic: boolean) {
+ return getVirtualStudyDescription(
+ this.props.description,
+ this.props.studyWithSamples,
+ this.props.filter,
+ this.attributeNamesSet,
+ this.props.molecularProfileNameSet,
+ this.props.caseListNameSet,
+ this.props.user,
+ dynamic
+ );
+ }
+
+ @computed get description() {
+ const noCustomDescriptionProvided =
+ this.customDescription == undefined ||
+ this.customDescription ===
+ this.getDefuaultDescriptionByType(!this.dynamic);
+ if (noCustomDescriptionProvided) {
+ return this.getDefuaultDescriptionByType(this.dynamic);
+ }
+ return this.customDescription || '';
+ }
+
render() {
return (
- (this.description =
+ (this.customDescription =
event.currentTarget.value)
}
/>
+
+
Type:
+
+
+ (this.dynamic = false)
+ }
+ />{' '}
+ Static
+
+
+
+ (this.dynamic = true)
+ }
+ />{' '}
+ Dynamic
+
+
+
+
+ Type of Virtual
+ Study:
+
+
+
+ This Virtual Study
+ will contain the set
+ of sample IDs
+ currently selected.
+ Furthermore, you can
+ define this Virtual
+ Study to be either
+ static or dynamic:
+
+
+
+
+ Static
+ {' '}
+ – Sample IDs are
+ the ones
+ currently
+ selected and no
+ new samples are
+ added to this
+ Virtual Study
+ set, even if the
+ database gets
+ updated with new
+ samples that
+ match the same
+ filtering/selection
+ criteria as the
+ samples in the
+ current set.
+
+
+
+ Dynamic
+ {' '}
+ – Unlike the
+ Static option,
+ any new samples
+ added to the
+ database that
+ match the
+ criteria of this
+ Virtual Study
+ will
+ automatically be
+ included in its
+ sample set.
+
+
+
+ }
+ >
+
+
+
+
{this.showSaveButton && (
void;
}
-export const VirtualStudyModal: React.FunctionComponent = function({
- appStore,
- pageStore,
- message,
- onHide,
-}) {
- return (
-
-
- Create a Virtual Study
-
-
- {message || null}
-
-
-
- );
-};
+export const VirtualStudyModal: React.FunctionComponent = observer(
+ ({ appStore, pageStore, message, onHide }) => {
+ const isLoading = [
+ pageStore.filteredVirtualStudies,
+ pageStore.studyWithSamples,
+ pageStore.filteredVirtualStudies,
+ pageStore.molecularProfileNameSet,
+ pageStore.molecularProfileNameSet,
+ ].some(result => result.isPending);
+ return (
+
+
+ Create a Virtual Study
+
+
+ {isLoading ? (
+
+ ) : (
+ <>
+ {message || null}
+
+ >
+ )}
+
+
+ );
+ }
+);
diff --git a/src/shared/api/urls.ts b/src/shared/api/urls.ts
index 771af857233..5ade1d701c6 100644
--- a/src/shared/api/urls.ts
+++ b/src/shared/api/urls.ts
@@ -139,6 +139,15 @@ export function getSampleViewUrlWithPathname(
return buildCBioPortalPageUrl(pathname, { sampleId, studyId }, hash);
}
+export function getResourceViewUrlWithPathname(
+ studyId: string,
+ pathname: string,
+ patientId: string
+) {
+ let caseId: string = `${patientId}`;
+ return buildCBioPortalPageUrl(pathname, { studyId, caseId });
+}
+
export function getPatientViewUrl(
studyId: string,
caseId: string,
diff --git a/src/shared/components/UsageAgreement.tsx b/src/shared/components/UsageAgreement.tsx
index 79bf1dbb833..d41ed3d7816 100644
--- a/src/shared/components/UsageAgreement.tsx
+++ b/src/shared/components/UsageAgreement.tsx
@@ -15,6 +15,7 @@ interface IUsageAgreement {
clauses: JSX.Element[];
useCheckboxes?: boolean;
expirationInDays?: number;
+ dismissButtonText?: string;
}
@observer
@@ -68,7 +69,6 @@ export default class UsageAgreement extends React.Component<
'true',
this.expirationInSeconds
);
- //localStorage.setItem(this.props.persistenceKey, 'true');
}
@autobind
@@ -111,7 +111,7 @@ export default class UsageAgreement extends React.Component<
bsSize="xsmall"
onClick={this.handleModalShow}
>
- Dismiss
+ {this.props.dismissButtonText || 'Dismiss'}
@@ -122,7 +122,7 @@ export default class UsageAgreement extends React.Component<
container={this}
aria-labelledby="contained-modal-title"
>
-
+
= ({
+ alphaMissensePrediction,
+ alphaMissenseScore,
+}) => {
+ const tooltipContent = () => {
+ const impact = alphaMissensePrediction ? (
+
+
+
+ Source
+
+ AlphaMissense
+
+
+
+ Impact
+
+
+ {alphaMissensePrediction}
+
+
+
+ {(alphaMissenseScore || alphaMissenseScore === 0) && (
+
+ Score
+
+ {alphaMissenseScore.toFixed(2)}
+
+
+ )}
+
+
+ ) : null;
+
+ return {impact} ;
+ };
+
+ let content: JSX.Element = (
+
+ );
+
+ if (alphaMissensePrediction && alphaMissensePrediction.length > 0) {
+ content = (
+
+
+
+ );
+ const arrowContent =
;
+ content = (
+
+ {content}
+
+ );
+ }
+
+ return content;
+};
diff --git a/src/shared/components/annotation/genomeNexus/MutationAssessor.tsx b/src/shared/components/annotation/genomeNexus/MutationAssessor.tsx
index 4665e3f3e37..76dfb84eb66 100644
--- a/src/shared/components/annotation/genomeNexus/MutationAssessor.tsx
+++ b/src/shared/components/annotation/genomeNexus/MutationAssessor.tsx
@@ -19,7 +19,8 @@ export default class MutationAssessor extends React.Component<
IMutationAssessorProps,
{}
> {
- static MUTATION_ASSESSOR_URL: string = 'http://mutationassessor.org/r3/';
+ // TODO Replace to new url when manuscript is available
+ // static MUTATION_ASSESSOR_URL: string = 'http://mutationassessor.org/r3/';
constructor(props: IMutationAssessorProps) {
super(props);
@@ -27,16 +28,6 @@ export default class MutationAssessor extends React.Component<
this.tooltipContent = this.tooltipContent.bind(this);
}
- public static download(
- mutationAssessorData: MutationAssessorData | undefined
- ): string {
- if (mutationAssessorData) {
- return `impact: ${mutationAssessorData.functionalImpact}, score: ${mutationAssessorData.functionalImpactScore}`;
- } else {
- return 'NA';
- }
- }
-
public render() {
let maContent: JSX.Element = (
@@ -44,7 +35,7 @@ export default class MutationAssessor extends React.Component<
if (
this.props.mutationAssessor &&
- this.props.mutationAssessor.functionalImpact !== null
+ this.props.mutationAssessor.functionalImpactPrediction !== null
) {
const maData = this.props.mutationAssessor;
maContent = (
@@ -52,7 +43,7 @@ export default class MutationAssessor extends React.Component<
className={classNames(
annotationStyles['annotation-item-text'],
(mutationAssessorColumn as any)[
- `ma-${maData.functionalImpact}`
+ `ma-${maData.functionalImpactPrediction}`
]
)}
>
@@ -80,15 +71,14 @@ export default class MutationAssessor extends React.Component<
private tooltipContent() {
if (this.props.mutationAssessor) {
const maData = this.props.mutationAssessor;
- const impact = maData.functionalImpact ? (
+ const impact = maData.functionalImpactPrediction ? (
Source
-
- MutationAssessor
-
+ {/* TODO Add link when manuscript is available */}
+ Mutation Assessor
@@ -97,11 +87,11 @@ export default class MutationAssessor extends React.Component<
- {maData.functionalImpact}
+ {maData.functionalImpactPrediction}
diff --git a/src/shared/components/annotation/genomeNexus/PolyPhen2.tsx b/src/shared/components/annotation/genomeNexus/PolyPhen2.tsx
index f532af56679..5e7485ac07c 100644
--- a/src/shared/components/annotation/genomeNexus/PolyPhen2.tsx
+++ b/src/shared/components/annotation/genomeNexus/PolyPhen2.tsx
@@ -22,17 +22,6 @@ export default class PolyPhen2 extends React.Component {
this.tooltipContent = this.tooltipContent.bind(this);
}
- public static download(
- polyPhenScore: number | undefined,
- polyPhenPrediction: string | undefined
- ): string {
- if (polyPhenScore || polyPhenPrediction) {
- return `impact: ${polyPhenPrediction}, score: ${polyPhenScore}`;
- } else {
- return 'NA';
- }
- }
-
public render() {
let content: JSX.Element = (
diff --git a/src/shared/components/annotation/genomeNexus/Sift.tsx b/src/shared/components/annotation/genomeNexus/Sift.tsx
index 92bf19734f2..70de4cf5d8b 100644
--- a/src/shared/components/annotation/genomeNexus/Sift.tsx
+++ b/src/shared/components/annotation/genomeNexus/Sift.tsx
@@ -22,17 +22,6 @@ export default class Sift extends React.Component {
this.tooltipContent = this.tooltipContent.bind(this);
}
- public static download(
- siftScore: number | undefined,
- siftPrediction: string | undefined
- ): string {
- if (siftScore || siftPrediction) {
- return `impact: ${siftPrediction}, score: ${siftScore}`;
- } else {
- return 'NA';
- }
- }
-
public render() {
let siftContent: JSX.Element = (
diff --git a/src/shared/components/annotation/genomeNexus/styles/alphaMissenseTooltip.module.scss b/src/shared/components/annotation/genomeNexus/styles/alphaMissenseTooltip.module.scss
new file mode 100644
index 00000000000..969f009fe00
--- /dev/null
+++ b/src/shared/components/annotation/genomeNexus/styles/alphaMissenseTooltip.module.scss
@@ -0,0 +1,22 @@
+@import 'functionalImpact';
+
+.alphaMissense-pathogenic {
+ color: $high;
+ font-weight: bold;
+}
+.alphaMissense-ambiguous {
+ color: $low;
+ font-weight: bold;
+}
+.alphaMissense-benign {
+ color: $neutral;
+ font-weight: bold;
+}
+.alphaMissense-unknown {
+ display: none;
+}
+.alphaMissense-tooltip-table {
+ td:first-child {
+ padding-right: 5px;
+ }
+}
diff --git a/src/shared/components/annotation/genomeNexus/styles/alphaMissenseTooltip.module.scss.d.ts b/src/shared/components/annotation/genomeNexus/styles/alphaMissenseTooltip.module.scss.d.ts
new file mode 100644
index 00000000000..82eec6b1f18
--- /dev/null
+++ b/src/shared/components/annotation/genomeNexus/styles/alphaMissenseTooltip.module.scss.d.ts
@@ -0,0 +1,9 @@
+declare const styles: {
+ readonly "alphaMissense-ambiguous": string;
+ readonly "alphaMissense-benign": string;
+ readonly "alphaMissense-pathogenic": string;
+ readonly "alphaMissense-tooltip-table": string;
+ readonly "alphaMissense-unknown": string;
+};
+export = styles;
+
diff --git a/src/shared/components/mutationTable/MutationTable.tsx b/src/shared/components/mutationTable/MutationTable.tsx
index ffe8ecfb5bb..cd3350961ed 100644
--- a/src/shared/components/mutationTable/MutationTable.tsx
+++ b/src/shared/components/mutationTable/MutationTable.tsx
@@ -899,14 +899,11 @@ export default class MutationTable<
this._columns[MutationTableColumnType.FUNCTIONAL_IMPACT] = {
name: MutationTableColumnType.FUNCTIONAL_IMPACT,
render: (d: Mutation[]) => {
- if (
- this.props.genomeNexusCache ||
- this.props.genomeNexusMutationAssessorCache
- ) {
+ if (this.props.genomeNexusMutationAssessorCache) {
return FunctionalImpactColumnFormatter.renderFunction(
d,
- this.props.genomeNexusCache,
- this.props.genomeNexusMutationAssessorCache
+ this.props.genomeNexusMutationAssessorCache,
+ this.props.selectedTranscriptId
);
} else {
return ;
@@ -915,9 +912,9 @@ export default class MutationTable<
download: (d: Mutation[]) =>
FunctionalImpactColumnFormatter.download(
d,
- this.props.genomeNexusCache as GenomeNexusCache,
this.props
- .genomeNexusMutationAssessorCache as GenomeNexusMutationAssessorCache
+ .genomeNexusMutationAssessorCache as GenomeNexusMutationAssessorCache,
+ this.props.selectedTranscriptId
),
headerRender: FunctionalImpactColumnFormatter.headerRender,
visible: false,
diff --git a/src/shared/components/mutationTable/column/FunctionalImpactColumnFormatter.tsx b/src/shared/components/mutationTable/column/FunctionalImpactColumnFormatter.tsx
index c3f418ae07f..c7d5ef26f4e 100644
--- a/src/shared/components/mutationTable/column/FunctionalImpactColumnFormatter.tsx
+++ b/src/shared/components/mutationTable/column/FunctionalImpactColumnFormatter.tsx
@@ -6,38 +6,33 @@ import {
TableCellStatusIndicator,
TableCellStatus,
} from 'cbioportal-frontend-commons';
-import {
- MutationAssessor as MutationAssessorData,
- VariantAnnotation,
-} from 'genome-nexus-ts-api-client';
+import { MutationAssessor as MutationAssessorData } from 'genome-nexus-ts-api-client';
import 'rc-tooltip/assets/bootstrap_white.css';
-import { Mutation, DiscreteCopyNumberData } from 'cbioportal-ts-api-client';
+import { Mutation } from 'cbioportal-ts-api-client';
import MutationAssessor from 'shared/components/annotation/genomeNexus/MutationAssessor';
import Sift from 'shared/components/annotation/genomeNexus/Sift';
import PolyPhen2 from 'shared/components/annotation/genomeNexus/PolyPhen2';
-import siftStyles from 'shared/components/annotation/genomeNexus/styles/siftTooltip.module.scss';
-import polyPhen2Styles from 'shared/components/annotation/genomeNexus/styles/polyPhen2Tooltip.module.scss';
-import mutationAssessorStyles from 'shared/components/annotation/genomeNexus/styles/mutationAssessorColumn.module.scss';
+import {
+ AlphaMissense,
+ AlphaMissenseUrl,
+} from 'shared/components/annotation/genomeNexus/AlphaMissense';
+import functionalImpactStyles from 'shared/components/annotation/genomeNexus/styles/mutationAssessorColumn.module.scss';
import annotationStyles from 'shared/components/annotation/styles/annotation.module.scss';
-import GenomeNexusMutationAssessorCache from 'shared/cache/GenomeNexusMutationAssessorCache';
import GenomeNexusCache, {
GenomeNexusCacheDataType,
-} from 'shared/cache/GenomeNexusCache';
+} from 'shared/cache/GenomeNexusMutationAssessorCache';
import _ from 'lodash';
import { shouldShowMutationAssessor } from 'shared/lib/genomeNexusAnnotationSourcesUtils';
type FunctionalImpactColumnTooltipProps = {
- active: 'mutationAssessor' | 'sift' | 'polyPhen2';
+ active: FunctionalImpactColumnName;
};
-interface IFunctionalImpactColumnTooltipState {
- active: 'mutationAssessor' | 'sift' | 'polyPhen2';
-}
-
-enum FunctionalImpactColumnsName {
- MUTATION_ASSESSOR,
- SIFT,
- POLYPHEN2,
+enum FunctionalImpactColumnName {
+ MUTATION_ASSESSOR = 'MUTATION_ASSESSOR',
+ SIFT = 'SIFT',
+ POLYPHEN2 = 'POLYPHEN2',
+ ALPHAMISSENSE = 'ALPHAMISSENSE',
}
interface FunctionalImpactData {
@@ -46,325 +41,235 @@ interface FunctionalImpactData {
siftPrediction: string | undefined;
polyPhenScore: number | undefined;
polyPhenPrediction: string | undefined;
+ alphaMissenseScore: number | undefined;
+ alphaMissensePrediction: string | undefined;
}
-class FunctionalImpactColumnTooltip extends React.Component<
- FunctionalImpactColumnTooltipProps,
- IFunctionalImpactColumnTooltipState
-> {
- constructor(props: FunctionalImpactColumnTooltipProps) {
- super(props);
- this.state = {
- active: this.props.active,
- };
- }
+const FunctionalImpactColumnTooltip: React.FC = ({
+ active: initialActive,
+}) => {
+ const [active, setActive] = React.useState(
+ initialActive
+ );
+
+ const showMutationAssessor = shouldShowMutationAssessor();
+
+ const renderHeaderIcon = (
+ title: string,
+ imageSrc: string,
+ onMouseOver: () => void
+ ) => {
+ return (
+
+
+
+
+
+ );
+ };
+
+ const renderImpactRow = (
+ iconClass: string,
+ impactData: string[],
+ showMutationAssessor: boolean,
+ key: number
+ ) => {
+ return (
+
+
+
+
+
+
+ {showMutationAssessor && (
+ {impactData[0]}
+ )}
+ {impactData[1]}
+ {impactData[2]}
+ {impactData[3]}
+
+ );
+ };
+
+ const legend = () => {
+ // Each line in the legend table uses the same style
+ const impactData = [
+ {
+ level: 'high',
+ iconClass: functionalImpactStyles['ma-high'],
+ mutationAssessor: 'high',
+ sift: 'deleterious',
+ polyPhen2: 'probably_damaging',
+ alphaMissense: 'pathogenic',
+ },
+ {
+ level: 'medium',
+ iconClass: functionalImpactStyles['ma-medium'],
+ mutationAssessor: 'medium',
+ sift: '-',
+ polyPhen2: '-',
+ alphaMissense: '-',
+ },
+ {
+ level: 'low',
+ iconClass: functionalImpactStyles['ma-low'],
+ mutationAssessor: 'low',
+ sift: 'deleterious_low_confidence',
+ polyPhen2: 'possibly_damaging',
+ alphaMissense: 'ambiguous',
+ },
+ {
+ level: 'neutral',
+ iconClass: functionalImpactStyles['ma-neutral'],
+ mutationAssessor: 'neutral',
+ sift: 'tolerated_low_confidence',
+ polyPhen2: 'benign',
+ alphaMissense: 'benign',
+ },
+ {
+ level: 'NA',
+ iconClass: functionalImpactStyles['ma-neutral'],
+ mutationAssessor: '-',
+ sift: 'tolerated',
+ polyPhen2: '-',
+ alphaMissense: '-',
+ },
+ ];
- legend() {
- const showMutationAssessor = shouldShowMutationAssessor();
return (
Legend
- {showMutationAssessor && (
-
-
- this.setState({
- active: 'mutationAssessor',
- })
- }
- >
-
-
-
+ {showMutationAssessor &&
+ renderHeaderIcon(
+ FunctionalImpactColumnName.MUTATION_ASSESSOR,
+ require('./mutationAssessor.png'),
+ () =>
+ setActive(
+ FunctionalImpactColumnName.MUTATION_ASSESSOR
+ )
+ )}
+ {renderHeaderIcon(
+ FunctionalImpactColumnName.SIFT,
+ require('./siftFunnel.png'),
+ () => setActive(FunctionalImpactColumnName.SIFT)
+ )}
+ {renderHeaderIcon(
+ FunctionalImpactColumnName.POLYPHEN2,
+ require('./polyPhen-2.png'),
+ () =>
+ setActive(
+ FunctionalImpactColumnName.POLYPHEN2
+ )
+ )}
+ {renderHeaderIcon(
+ FunctionalImpactColumnName.ALPHAMISSENSE,
+ require('./alphaMissenseGoogleDeepmind.png'),
+ () =>
+ setActive(
+ FunctionalImpactColumnName.ALPHAMISSENSE
+ )
)}
-
-
- this.setState({ active: 'sift' })
- }
- >
-
-
-
-
-
- this.setState({ active: 'polyPhen2' })
- }
- >
-
-
-
-
-
-
-
-
-
- {showMutationAssessor && (
-
- high
-
- )}
-
- deleterious
-
-
- probably_damaging
-
-
- {showMutationAssessor && (
-
-
-
-
-
-
-
- medium
-
- -
- -
-
+ {impactData.map((data, index) =>
+ renderImpactRow(
+ data.iconClass,
+ [
+ data.mutationAssessor,
+ data.sift,
+ data.polyPhen2,
+ data.alphaMissense,
+ ],
+ showMutationAssessor,
+ index
+ )
)}
-
-
-
-
-
-
- {showMutationAssessor && (
-
- low
-
- )}
-
- deleterious_low_confidence
-
-
- possibly_damaging
-
-
-
-
-
-
-
-
- {showMutationAssessor && (
-
- neutral
-
- )}
-
- tolerated_low_confidence
-
-
- benign
-
-
-
-
-
-
-
-
- {showMutationAssessor && - }
-
- tolerated
-
- -
-
);
- }
+ };
- public static mutationAssessorText() {
- return (
-
-
- Mutation Assessor
- {' '}
- predicts the functional impact of amino-acid substitutions in
- proteins, such as mutations discovered in cancer or missense
- polymorphisms. The functional impact is assessed based on
- evolutionary conservation of the affected amino acid in protein
- homologs. The method has been validated on a large set (60k) of
- disease associated (OMIM) and polymorphic variants.
-
- );
- }
-
- public static siftText() {
- return (
-
-
- SIFT
- {' '}
- predicts whether an amino acid substitution affects protein
- function based on sequence homology and the physical properties
- of amino acids. SIFT can be applied to naturally occurring
- nonsynonymous polymorphisms and laboratory-induced missense
- mutations.
-
- );
- }
-
- public static polyPhen2Text() {
- return (
-
-
- PolyPhen-2
- {' '}
- (Polymorphism Phenotyping v2) is a tool which predicts possible
- impact of an amino acid substitution on the structure and
- function of a human protein using straightforward physical and
- comparative considerations.
-
- );
- }
-
- public render() {
- return (
-
- {this.state.active === 'mutationAssessor' &&
- FunctionalImpactColumnTooltip.mutationAssessorText()}
- {this.state.active === 'sift' &&
- FunctionalImpactColumnTooltip.siftText()}
- {this.state.active === 'polyPhen2' &&
- FunctionalImpactColumnTooltip.polyPhen2Text()}
- {this.legend()}
-
- );
- }
-}
+ return (
+
+ {active === FunctionalImpactColumnName.MUTATION_ASSESSOR && (
+
+ Mutation Assessor predicts the functional impact of
+ amino-acid substitutions in proteins, such as mutations
+ discovered in cancer or missense polymorphisms. The
+ functional impact is assessed based on evolutionary
+ conservation of the affected amino acid in protein homologs.
+ The method has been validated on a large set of disease
+ associated and polymorphic variants (
+
+ ClinVar
+
+ ).
+
+
+ Mutation Assessor V4 data is available in the portal
+ since Oct. 8, 2024.
+ {' '}
+ New manuscript is in progress. Click{` `}
+
+ here
+
+ {` `} to see information about V3 data.
+
+ )}
+ {active === FunctionalImpactColumnName.SIFT && (
+
+
+ SIFT
+ {' '}
+ predicts whether an amino acid substitution affects protein
+ function based on sequence homology and the physical
+ properties of amino acids. SIFT can be applied to naturally
+ occurring nonsynonymous polymorphisms and laboratory-induced
+ missense mutations.
+
+ )}
+ {active === FunctionalImpactColumnName.POLYPHEN2 && (
+
+
+ PolyPhen-2
+ {' '}
+ (Polymorphism Phenotyping v2) is a tool which predicts
+ possible impact of an amino acid substitution on the
+ structure and function of a human protein using
+ straightforward physical and comparative considerations.
+
+ )}
+ {active === FunctionalImpactColumnName.ALPHAMISSENSE && (
+
+
+ AlphaMissense
+ {' '}
+ predicts the probability of a missense single nucleotide
+ variant being pathogenic and classifies it as either likely
+ benign, likely pathogenic, or uncertain.
+
+ )}
+ {legend()}
+
+ );
+};
export function placeArrow(tooltipEl: any) {
const arrowEl = tooltipEl.querySelector('.rc-tooltip-arrow');
@@ -383,7 +288,11 @@ export default class FunctionalImpactColumnFormatter {
{showMutationAssessor && (
+
}
placement="topLeft"
trigger={['hover', 'focus']}
@@ -405,7 +314,9 @@ export default class FunctionalImpactColumnFormatter {
)}
+
}
placement="topLeft"
trigger={['hover', 'focus']}
@@ -424,7 +335,9 @@ export default class FunctionalImpactColumnFormatter {
+
}
placement="topLeft"
trigger={['hover', 'focus']}
@@ -441,180 +354,146 @@ export default class FunctionalImpactColumnFormatter {
/>
+
+ }
+ placement="topLeft"
+ trigger={['hover', 'focus']}
+ arrowContent={arrowContent}
+ destroyTooltipOnHide={true}
+ onPopupAlign={placeArrow}
+ >
+
+
+
+
);
}
- public static getData(
+ static getData(
data: Mutation[],
- siftPolyphenCache: GenomeNexusCache,
- mutationAssessorCache: GenomeNexusMutationAssessorCache
+ cache?: GenomeNexusCache,
+ selectedTranscriptId?: string
): FunctionalImpactData {
- const siftPolyphenCacheData = FunctionalImpactColumnFormatter.getDataFromCache(
- data,
- siftPolyphenCache
- );
- const mutationAssessorCacheData = shouldShowMutationAssessor()
- ? FunctionalImpactColumnFormatter.getDataFromCache(
- data,
- mutationAssessorCache
- )
- : null;
-
- const siftData = siftPolyphenCacheData
- ? this.getSiftData(siftPolyphenCacheData.data)
- : undefined;
- const polyphenData = siftPolyphenCacheData
- ? this.getPolyphenData(siftPolyphenCacheData.data)
- : undefined;
- const mutationAssessor = mutationAssessorCacheData
- ? this.getMutationAssessorData(mutationAssessorCacheData.data)
- : undefined;
-
- const siftScore = siftData && siftData.siftScore;
- const siftPrediction = siftData && siftData.siftPrediction;
- const polyPhenScore = polyphenData && polyphenData.polyPhenScore;
- const polyPhenPrediction =
- polyphenData && polyphenData.polyPhenPrediction;
-
- const functionalImpactData: FunctionalImpactData = {
- mutationAssessor,
- siftScore,
- siftPrediction,
- polyPhenScore,
- polyPhenPrediction,
- };
- return functionalImpactData;
- }
-
- public static getSiftData(siftDataCache: VariantAnnotation | null) {
- let siftScore: number | undefined = undefined;
- let siftPrediction: string | undefined = undefined;
-
- if (
- siftDataCache &&
- !_.isEmpty(siftDataCache.transcript_consequences)
- ) {
- siftScore = siftDataCache.transcript_consequences[0].sift_score;
- siftPrediction =
- siftDataCache.transcript_consequences[0].sift_prediction;
+ const cacheData = this.getDataFromCache(data, cache);
+ if (!cacheData?.data) {
+ return {} as FunctionalImpactData;
}
- return {
- siftScore,
- siftPrediction,
- };
- }
-
- public static getPolyphenData(polyphenDataCache: VariantAnnotation | null) {
- let polyPhenScore: number | undefined = undefined;
- let polyPhenPrediction: string | undefined = undefined;
-
- if (
- polyphenDataCache &&
- !_.isEmpty(polyphenDataCache.transcript_consequences)
- ) {
- polyPhenScore =
- polyphenDataCache.transcript_consequences[0].polyphen_score;
- polyPhenPrediction =
- polyphenDataCache.transcript_consequences[0]
- .polyphen_prediction;
- }
+ const transcript = selectedTranscriptId
+ ? cacheData.data.transcript_consequences.find(
+ tc => tc.transcript_id === selectedTranscriptId
+ )
+ : undefined;
return {
- polyPhenScore,
- polyPhenPrediction,
+ mutationAssessor: shouldShowMutationAssessor()
+ ? cacheData.data.mutation_assessor
+ : undefined,
+ siftScore: transcript?.sift_score,
+ siftPrediction: transcript?.sift_prediction,
+ polyPhenScore: transcript?.polyphen_score,
+ polyPhenPrediction: transcript?.polyphen_prediction,
+ alphaMissenseScore: transcript?.alphaMissense?.score,
+ alphaMissensePrediction: transcript?.alphaMissense?.pathogenicity,
};
}
- public static getMutationAssessorData(
- mutationAssessorDataCache: VariantAnnotation | null
- ): MutationAssessorData | undefined {
- if (!mutationAssessorDataCache) {
- return undefined;
- } else {
- return mutationAssessorDataCache.mutation_assessor
- ? mutationAssessorDataCache.mutation_assessor.annotation
- : undefined;
- }
- }
-
public static renderFunction(
data: Mutation[],
- siftPolyphenCache: GenomeNexusCache | undefined,
- mutationAssessorCache: GenomeNexusMutationAssessorCache | undefined
+ genomeNexusCache: GenomeNexusCache | undefined,
+ selectedTranscriptId?: string
) {
const showMutationAssessor = shouldShowMutationAssessor();
- const siftPolyphenCacheData = FunctionalImpactColumnFormatter.getDataFromCache(
- data,
- siftPolyphenCache
- );
- const mutationAssessorCacheData = showMutationAssessor
- ? FunctionalImpactColumnFormatter.getDataFromCache(
- data,
- mutationAssessorCache
- )
- : null;
return (
{showMutationAssessor &&
FunctionalImpactColumnFormatter.makeFunctionalImpactViz(
- mutationAssessorCacheData,
- FunctionalImpactColumnsName.MUTATION_ASSESSOR
+ data,
+ FunctionalImpactColumnName.MUTATION_ASSESSOR,
+ genomeNexusCache,
+ selectedTranscriptId
)}
{FunctionalImpactColumnFormatter.makeFunctionalImpactViz(
- siftPolyphenCacheData,
- FunctionalImpactColumnsName.SIFT
+ data,
+ FunctionalImpactColumnName.SIFT,
+ genomeNexusCache,
+ selectedTranscriptId
)}
{FunctionalImpactColumnFormatter.makeFunctionalImpactViz(
- siftPolyphenCacheData,
- FunctionalImpactColumnsName.POLYPHEN2
+ data,
+ FunctionalImpactColumnName.POLYPHEN2,
+ genomeNexusCache,
+ selectedTranscriptId
+ )}
+ {FunctionalImpactColumnFormatter.makeFunctionalImpactViz(
+ data,
+ FunctionalImpactColumnName.ALPHAMISSENSE,
+ genomeNexusCache,
+ selectedTranscriptId
)}
);
}
- public static download(
+ static download(
data: Mutation[],
- siftPolyphenCache: GenomeNexusCache,
- mutationAssessorCache: GenomeNexusMutationAssessorCache
+ cache: GenomeNexusCache,
+ selectedTranscriptId?: string
): string {
- if (siftPolyphenCache || mutationAssessorCache) {
- const functionalImpactData = FunctionalImpactColumnFormatter.getData(
- data,
- siftPolyphenCache,
- mutationAssessorCache
- );
- let downloadData = [];
- if (functionalImpactData) {
- if (shouldShowMutationAssessor()) {
- downloadData.push(
- `MutationAssessor: ${MutationAssessor.download(
- functionalImpactData.mutationAssessor
- )}`
- );
- }
- downloadData = downloadData.concat([
- `SIFT: ${Sift.download(
- functionalImpactData.siftScore,
- functionalImpactData.siftPrediction
- )}`,
- `Polyphen-2: ${PolyPhen2.download(
- functionalImpactData.polyPhenScore,
- functionalImpactData.polyPhenPrediction
- )}`,
- ]);
- return downloadData.join(';');
- }
- }
- return '';
+ const functionalImpactData = this.getData(
+ data,
+ cache,
+ selectedTranscriptId
+ );
+ if (!functionalImpactData) return '';
+
+ const downloadData = [
+ shouldShowMutationAssessor() &&
+ `MutationAssessor: ${
+ functionalImpactData.mutationAssessor
+ ? `impact: ${functionalImpactData.mutationAssessor.functionalImpactPrediction}, score: ${functionalImpactData.mutationAssessor.functionalImpactScore}`
+ : 'NA'
+ }`,
+ `SIFT: ${
+ functionalImpactData.siftScore ||
+ functionalImpactData.siftPrediction
+ ? `impact: ${functionalImpactData.siftPrediction}, score: ${functionalImpactData.siftScore}`
+ : 'NA'
+ }`,
+ `Polyphen-2: ${
+ functionalImpactData.polyPhenScore ||
+ functionalImpactData.polyPhenPrediction
+ ? `impact: ${functionalImpactData.polyPhenPrediction}, score: ${functionalImpactData.polyPhenScore}`
+ : 'NA'
+ }`,
+ `AlphaMissense: ${
+ functionalImpactData.alphaMissenseScore ||
+ functionalImpactData.alphaMissensePrediction
+ ? `pathogenicity: ${functionalImpactData.alphaMissensePrediction}, score: ${functionalImpactData.alphaMissenseScore}`
+ : 'NA'
+ }`,
+ ];
+
+ return downloadData.join(';');
}
private static getDataFromCache(
data: Mutation[],
- cache: GenomeNexusCache | GenomeNexusMutationAssessorCache | undefined
+ cache: GenomeNexusCache | GenomeNexusCache | undefined
): GenomeNexusCacheDataType | null {
if (data.length === 0 || !cache) {
return null;
@@ -623,11 +502,13 @@ export default class FunctionalImpactColumnFormatter {
}
private static makeFunctionalImpactViz(
- cacheData: GenomeNexusCacheDataType | null,
- column: FunctionalImpactColumnsName
+ mutation: Mutation[],
+ column: FunctionalImpactColumnName,
+ genomeNexusCache?: GenomeNexusCache,
+ selectedTranscriptId?: string
) {
let status: TableCellStatus | null = null;
-
+ const cacheData = this.getDataFromCache(mutation, genomeNexusCache);
if (cacheData === null) {
status = TableCellStatus.LOADING;
} else if (cacheData.status === 'error') {
@@ -635,36 +516,38 @@ export default class FunctionalImpactColumnFormatter {
} else if (cacheData.data === null) {
status = TableCellStatus.NA;
} else {
- let functionalImpactData;
+ const data = this.getData(
+ mutation,
+ genomeNexusCache,
+ selectedTranscriptId
+ );
switch (column) {
- case FunctionalImpactColumnsName.MUTATION_ASSESSOR:
- functionalImpactData = FunctionalImpactColumnFormatter.getMutationAssessorData(
- cacheData.data
- );
+ case FunctionalImpactColumnName.MUTATION_ASSESSOR:
return (
);
- case FunctionalImpactColumnsName.SIFT:
- functionalImpactData = FunctionalImpactColumnFormatter.getSiftData(
- cacheData.data
- );
+ case FunctionalImpactColumnName.SIFT:
return (
);
- case FunctionalImpactColumnsName.POLYPHEN2:
- functionalImpactData = FunctionalImpactColumnFormatter.getPolyphenData(
- cacheData.data
- );
+ case FunctionalImpactColumnName.POLYPHEN2:
return (
+ );
+ case FunctionalImpactColumnName.ALPHAMISSENSE:
+ return (
+
);
diff --git a/src/shared/components/mutationTable/column/alphaMissenseGoogleDeepmind.png b/src/shared/components/mutationTable/column/alphaMissenseGoogleDeepmind.png
new file mode 100644
index 00000000000..360dce5d2f9
Binary files /dev/null and b/src/shared/components/mutationTable/column/alphaMissenseGoogleDeepmind.png differ
diff --git a/src/shared/components/oncoprint/Oncoprint.tsx b/src/shared/components/oncoprint/Oncoprint.tsx
index 48e01dc6d59..ccb9653ecab 100644
--- a/src/shared/components/oncoprint/Oncoprint.tsx
+++ b/src/shared/components/oncoprint/Oncoprint.tsx
@@ -22,6 +22,7 @@ import {
} from 'shared/model/AnnotatedMutation';
import { CustomDriverNumericGeneMolecularData } from 'shared/model/CustomDriverNumericGeneMolecularData';
import { ExtendedAlteration } from 'shared/model/ExtendedAlteration';
+import { GAP_MODE_ENUM } from 'oncoprintjs';
export type CategoricalTrackDatum = {
entity: string;
@@ -90,7 +91,7 @@ export class ClinicalTrackConfig {
export type ClinicalTrackConfigChange = {
stableId?: string;
sortOrder?: string;
- gapOn?: boolean;
+ gapMode?: GAP_MODE_ENUM;
};
export type ClinicalTrackConfigMap = {
@@ -313,7 +314,7 @@ export interface IOncoprintProps {
onDeleteClinicalTrack?: (key: string) => void;
onDeleteGeneticTrack?: (key: string, sublabel: string) => void;
onTrackSortDirectionChange?: (trackId: TrackId, dir: number) => void;
- onTrackGapChange?: (trackId: TrackId, gap: boolean) => void;
+ onTrackGapChange?: (trackId: TrackId, gap: GAP_MODE_ENUM) => void;
trackKeySelectedForEdit?: string | null;
setTrackKeySelectedForEdit?: (key: string | null) => void;
diff --git a/src/shared/components/oncoprint/ResultsViewOncoprint.tsx b/src/shared/components/oncoprint/ResultsViewOncoprint.tsx
index 89772ce219d..aee6055c16f 100644
--- a/src/shared/components/oncoprint/ResultsViewOncoprint.tsx
+++ b/src/shared/components/oncoprint/ResultsViewOncoprint.tsx
@@ -101,6 +101,7 @@ import classnames from 'classnames';
import { OncoprintColorModal } from './OncoprintColorModal';
import JupyterNoteBookModal from 'pages/staticPages/tools/oncoprinter/JupyterNotebookModal';
import { convertToCSV } from 'shared/lib/calculation/JSONtoCSV';
+import { GAP_MODE_ENUM } from 'oncoprintjs';
interface IResultsViewOncoprintProps {
divId: string;
@@ -1671,9 +1672,8 @@ export default class ResultsViewOncoprint extends React.Component<
* Called when a track gap is added from within oncoprintjs UI
*/
@action.bound
- @action.bound
- private onTrackGapChange(trackId: TrackId, gapOn: boolean) {
- this.handleClinicalTrackChange(trackId, { gapOn });
+ private onTrackGapChange(trackId: TrackId, mode: GAP_MODE_ENUM) {
+ this.handleClinicalTrackChange(trackId, { gapMode: mode });
}
private handleClinicalTrackChange(
diff --git a/src/shared/components/plots/PlotsTab.tsx b/src/shared/components/plots/PlotsTab.tsx
index 814df5b16f7..d1890336965 100644
--- a/src/shared/components/plots/PlotsTab.tsx
+++ b/src/shared/components/plots/PlotsTab.tsx
@@ -18,6 +18,7 @@ import _ from 'lodash';
import {
axisHasNegativeNumbers,
boxPlotTooltip,
+ CUSTOM_ATTR_DATA_TYPE,
CLIN_ATTR_DATA_TYPE,
CNA_STROKE_WIDTH,
dataTypeDisplayOrder,
@@ -277,6 +278,7 @@ export interface IPlotsTabProps {
sampleKeyToSample: MobxPromise<_.Dictionary>;
genes: MobxPromise;
clinicalAttributes: MobxPromise;
+ customAttributes: MobxPromise;
genesets: MobxPromise;
genericAssayEntitiesGroupByMolecularProfileId: MobxPromise<{
[profileId: string]: GenericAssayMeta[];
@@ -696,6 +698,31 @@ export default class PlotsTab extends React.Component {
);
}
break;
+ case CUSTOM_ATTR_DATA_TYPE:
+ if (
+ this.horzSelection.dataSourceId !== undefined &&
+ this.customAttributesGroupByclinicalAttributeId.isComplete
+ ) {
+ const attributes = this
+ .customAttributesGroupByclinicalAttributeId.result![
+ this.horzSelection.dataSourceId
+ ];
+ const studyIds = attributes.map(
+ attribute => attribute.studyId
+ );
+ horzAxisStudies = this.props.studies.result.filter(study =>
+ studyIds.includes(study.studyId)
+ );
+ components.push(
+
+ Horizontal Axis:
+ {`${horzAxisDataSampleCount} samples from ${
+ horzAxisStudies.length
+ } ${Pluralize('study', horzAxisStudies.length)}`}
+
+ );
+ }
+ break;
default:
// molecular profile
if (
@@ -731,6 +758,31 @@ export default class PlotsTab extends React.Component {
case NONE_SELECTED_OPTION_STRING_VALUE:
isVertAxisNoneOptionSelected = true;
break;
+ case CUSTOM_ATTR_DATA_TYPE:
+ if (
+ this.vertSelection.dataSourceId !== undefined &&
+ this.customAttributesGroupByclinicalAttributeId.isComplete
+ ) {
+ const attributes = this
+ .customAttributesGroupByclinicalAttributeId.result![
+ this.vertSelection.dataSourceId
+ ];
+ const studyIds = attributes.map(
+ attribute => attribute.studyId
+ );
+ vertAxisStudies = this.props.studies.result.filter(study =>
+ studyIds.includes(study.studyId)
+ );
+ components.push(
+
+ Vertical Axis:
+ {`${vertAxisDataSampleCount} samples from ${
+ vertAxisStudies.length
+ } ${Pluralize('study', vertAxisStudies.length)}`}
+
+ );
+ }
+ break;
case CLIN_ATTR_DATA_TYPE:
if (
this.vertSelection.dataSourceId !== undefined &&
@@ -1121,9 +1173,10 @@ export default class PlotsTab extends React.Component {
this._selectedGenesetOption &&
this._selectedGenesetOption.value ===
SAME_SELECTED_OPTION_STRING_VALUE &&
- self.horzSelection.dataType === CLIN_ATTR_DATA_TYPE
+ (self.horzSelection.dataType === CLIN_ATTR_DATA_TYPE ||
+ self.horzSelection.dataType === CUSTOM_ATTR_DATA_TYPE)
) {
- // if vertical gene set option is "same as horizontal", and horizontal is clinical, then use the actual
+ // if vertical gene set option is "same as horizontal", and horizontal is clinical or custom, then use the actual
// gene set option value instead of "Same gene" option value, because that would be slightly weird UX
return self.horzSelection.selectedGenesetOption;
} else {
@@ -1195,9 +1248,10 @@ export default class PlotsTab extends React.Component {
this._selectedGenericAssayOption &&
this._selectedGenericAssayOption.value ===
SAME_SELECTED_OPTION_STRING_VALUE &&
- self.horzSelection.dataType === CLIN_ATTR_DATA_TYPE
+ (self.horzSelection.dataType === CLIN_ATTR_DATA_TYPE ||
+ self.horzSelection.dataType === CUSTOM_ATTR_DATA_TYPE)
) {
- // if vertical gene set option is "same as horizontal", and horizontal is clinical, then use the actual
+ // if vertical gene set option is "same as horizontal", and horizontal is clinical or custom, then use the actual
// gene set option value instead of "Same gene" option value, because that would be slightly weird UX
return self.horzSelection.selectedGenericAssayOption;
} else {
@@ -2063,10 +2117,45 @@ export default class PlotsTab extends React.Component {
},
});
+ readonly clinicalAndCustomAttributes = remoteData<
+ ExtendedClinicalAttribute[]
+ >({
+ await: () => [
+ this.props.clinicalAttributes,
+ this.props.customAttributes,
+ ],
+ invoke: () => {
+ return Promise.resolve([
+ ...this.props.clinicalAttributes.result!,
+ ...this.props.customAttributes.result!,
+ ]);
+ },
+ });
+
+ readonly clinicalAttributeOptions = remoteData({
+ await: () => [this.props.clinicalAttributes],
+ invoke: () =>
+ Promise.resolve(
+ makeClinicalAttributeOptions(
+ this.props.clinicalAttributes.result!
+ )
+ ),
+ });
+
+ readonly customAttributeOptions = remoteData({
+ await: () => [this.props.customAttributes],
+ invoke: () =>
+ Promise.resolve(
+ makeClinicalAttributeOptions(
+ this.props.customAttributes.result!
+ )
+ ),
+ });
+
readonly coloringMenuOmnibarOptions = remoteData<
(ColoringMenuOmnibarOption | ColoringMenuOmnibarGroup)[]
>({
- await: () => [this.props.genes, this.props.clinicalAttributes],
+ await: () => [this.props.genes, this.clinicalAndCustomAttributes],
invoke: () => {
const allOptions: (
| Omit
@@ -2088,7 +2177,7 @@ export default class PlotsTab extends React.Component {
allOptions.push({
label: 'Clinical Attributes',
- options: this.props.clinicalAttributes
+ options: this.clinicalAndCustomAttributes
.result!.filter(a => {
return (
a.clinicalAttributeId !==
@@ -2104,7 +2193,6 @@ export default class PlotsTab extends React.Component {
};
}),
});
-
if (allOptions.length > 0) {
// add 'None' option to the top of the list to allow removing coloring of samples
allOptions.unshift({
@@ -2365,6 +2453,7 @@ export default class PlotsTab extends React.Component {
dataType !== NONE_SELECTED_OPTION_STRING_VALUE &&
dataType !== GENESET_DATA_TYPE &&
dataType !== CLIN_ATTR_DATA_TYPE &&
+ dataType !== CUSTOM_ATTR_DATA_TYPE &&
!isGenericAssaySelected
);
}
@@ -2408,12 +2497,27 @@ export default class PlotsTab extends React.Component {
readonly clinicalAttributeIdToClinicalAttribute = remoteData<{
[clinicalAttributeId: string]: ClinicalAttribute;
}>({
- await: () => [this.props.clinicalAttributes, this.props.studyIds],
+ await: () => [this.props.studyIds, this.props.clinicalAttributes],
invoke: () => {
let _map: {
[clinicalAttributeId: string]: ClinicalAttribute;
} = _.keyBy(
- this.props.clinicalAttributes.result,
+ this.props.clinicalAttributes.result!,
+ c => c.clinicalAttributeId
+ );
+ return Promise.resolve(_map);
+ },
+ });
+
+ readonly customAttributeIdToClinicalAttribute = remoteData<{
+ [clinicalAttributeId: string]: ClinicalAttribute;
+ }>({
+ await: () => [this.props.studyIds, this.props.customAttributes],
+ invoke: () => {
+ let _map: {
+ [clinicalAttributeId: string]: ClinicalAttribute;
+ } = _.keyBy(
+ this.props.customAttributes.result!,
c => c.clinicalAttributeId
);
return Promise.resolve(_map);
@@ -2427,21 +2531,25 @@ export default class PlotsTab extends React.Component {
invoke: () => {
return Promise.resolve(
_.groupBy(
- this.props.clinicalAttributes.result,
+ this.props.clinicalAttributes.result!,
c => c.clinicalAttributeId
)
);
},
});
- readonly clinicalAttributeOptions = remoteData({
- await: () => [this.props.clinicalAttributes],
- invoke: () =>
- Promise.resolve(
- makeClinicalAttributeOptions(
- this.props.clinicalAttributes.result!
+ readonly customAttributesGroupByclinicalAttributeId = remoteData<{
+ [clinicalAttributeId: string]: ClinicalAttribute[];
+ }>({
+ await: () => [this.props.customAttributes],
+ invoke: () => {
+ return Promise.resolve(
+ _.groupBy(
+ this.props.customAttributes.result!,
+ c => c.clinicalAttributeId
)
- ),
+ );
+ },
});
readonly dataTypeOptions = remoteData({
@@ -2450,6 +2558,7 @@ export default class PlotsTab extends React.Component {
this.clinicalAttributeOptions,
this.props.molecularProfilesInStudies,
this.props.genesets,
+ this.customAttributeOptions,
],
invoke: () => {
const profiles = this.props.molecularProfilesWithData.result!;
@@ -2476,6 +2585,10 @@ export default class PlotsTab extends React.Component {
dataTypeIds.push(CLIN_ATTR_DATA_TYPE);
}
+ if (!_.isEmpty(this.customAttributeOptions.result)) {
+ dataTypeIds.push(CUSTOM_ATTR_DATA_TYPE);
+ }
+
if (
this.props.molecularProfilesInStudies.result!.length &&
this.horzGenesetOptions.result &&
@@ -2532,6 +2645,7 @@ export default class PlotsTab extends React.Component {
await: () => [
this.props.molecularProfilesInStudies,
this.clinicalAttributeOptions,
+ this.customAttributeOptions,
],
invoke: () => {
const profiles = this.props.molecularProfilesInStudies.result!;
@@ -2584,6 +2698,13 @@ export default class PlotsTab extends React.Component {
CLIN_ATTR_DATA_TYPE
] = this.clinicalAttributeOptions.result!;
}
+
+ if (!_.isEmpty(this.customAttributeOptions.result)) {
+ // add custom attributes
+ map[
+ CUSTOM_ATTR_DATA_TYPE
+ ] = this.customAttributeOptions.result!;
+ }
return Promise.resolve(map);
},
});
@@ -2686,6 +2807,7 @@ export default class PlotsTab extends React.Component {
@computed get hasMolecularProfile() {
return (dataType: string | undefined) =>
dataType !== CLIN_ATTR_DATA_TYPE &&
+ dataType !== CUSTOM_ATTR_DATA_TYPE &&
dataType !== AlterationTypeConstants.GENERIC_ASSAY;
}
@@ -2822,6 +2944,7 @@ export default class PlotsTab extends React.Component {
selection.dataType !== undefined &&
selection.dataType !== NONE_SELECTED_OPTION_STRING_VALUE &&
selection.dataType !== CLIN_ATTR_DATA_TYPE &&
+ selection.dataType !== CUSTOM_ATTR_DATA_TYPE &&
selection.dataType !== GENESET_DATA_TYPE &&
!isGenericAssaySelected(selection)
);
@@ -3048,6 +3171,7 @@ export default class PlotsTab extends React.Component {
return makeAxisDataPromise(
this.horzSelection,
this.clinicalAttributeIdToClinicalAttribute,
+ this.customAttributeIdToClinicalAttribute,
this.props.molecularProfileIdSuffixToMolecularProfiles,
this.props.patientKeyToFilteredSamples,
this.props.entrezGeneIdToGene,
@@ -3075,6 +3199,7 @@ export default class PlotsTab extends React.Component {
return makeAxisDataPromise(
this.vertSelection,
this.clinicalAttributeIdToClinicalAttribute,
+ this.customAttributeIdToClinicalAttribute,
this.props.molecularProfileIdSuffixToMolecularProfiles,
this.props.patientKeyToFilteredSamples,
this.props.entrezGeneIdToGene,
@@ -3154,6 +3279,7 @@ export default class PlotsTab extends React.Component {
this.props.molecularProfileIdSuffixToMolecularProfiles,
this.props.entrezGeneIdToGene,
this.clinicalAttributeIdToClinicalAttribute,
+ this.customAttributeIdToClinicalAttribute,
this.plotType,
],
invoke: () => {
@@ -3164,6 +3290,7 @@ export default class PlotsTab extends React.Component {
.result!,
this.props.entrezGeneIdToGene.result!,
this.clinicalAttributeIdToClinicalAttribute.result!,
+ this.customAttributeIdToClinicalAttribute.result!,
this.horzLogScaleFunction
)
);
@@ -3175,6 +3302,7 @@ export default class PlotsTab extends React.Component {
this.props.molecularProfileIdSuffixToMolecularProfiles,
this.props.entrezGeneIdToGene,
this.clinicalAttributeIdToClinicalAttribute,
+ this.customAttributeIdToClinicalAttribute,
],
invoke: () => {
return Promise.resolve(
@@ -3184,6 +3312,7 @@ export default class PlotsTab extends React.Component {
.result!,
this.props.entrezGeneIdToGene.result!,
this.clinicalAttributeIdToClinicalAttribute.result!,
+ this.customAttributeIdToClinicalAttribute.result!,
this.vertLogScaleFunction
)
);
@@ -3195,6 +3324,7 @@ export default class PlotsTab extends React.Component {
this.props.molecularProfileIdSuffixToMolecularProfiles,
this.props.entrezGeneIdToGene,
this.clinicalAttributeIdToClinicalAttribute,
+ this.customAttributeIdToClinicalAttribute,
this.plotType,
],
invoke: () => {
@@ -3212,6 +3342,7 @@ export default class PlotsTab extends React.Component {
.result!,
this.props.entrezGeneIdToGene.result!,
this.clinicalAttributeIdToClinicalAttribute.result!,
+ this.customAttributeIdToClinicalAttribute.result!,
logScaleFunc
)
);
@@ -3564,6 +3695,9 @@ export default class PlotsTab extends React.Component {
: structuralVariantCountByOptions;
switch (axisSelection.dataType) {
+ case CUSTOM_ATTR_DATA_TYPE:
+ dataSourceLabel = 'Custom Attribute';
+ break;
case CLIN_ATTR_DATA_TYPE:
dataSourceLabel = 'Clinical Attribute';
break;
@@ -3609,6 +3743,11 @@ export default class PlotsTab extends React.Component {
.clinicalAttributeIdToClinicalAttribute.result![
dataSourceValue
].description;
+ } else if (axisSelection.dataType === CUSTOM_ATTR_DATA_TYPE) {
+ dataSourceDescription = this
+ .customAttributeIdToClinicalAttribute.result![
+ dataSourceValue
+ ].description;
} else {
dataSourceDescription = this.props
.molecularProfileIdSuffixToMolecularProfiles.result![
@@ -3939,7 +4078,9 @@ export default class PlotsTab extends React.Component {
style={{
display:
axisSelection.dataType ===
- CLIN_ATTR_DATA_TYPE
+ CLIN_ATTR_DATA_TYPE ||
+ axisSelection.dataType ===
+ CUSTOM_ATTR_DATA_TYPE
? 'none'
: 'block',
}}
@@ -3979,7 +4120,9 @@ export default class PlotsTab extends React.Component {
axisSelection.dataType ===
CLIN_ATTR_DATA_TYPE ||
axisSelection.dataType ===
- GENESET_DATA_TYPE
+ GENESET_DATA_TYPE ||
+ axisSelection.dataType ===
+ CUSTOM_ATTR_DATA_TYPE
}
loadOptions={loadOptions}
cacheOptions={true}
@@ -4017,7 +4160,9 @@ export default class PlotsTab extends React.Component {
axisSelection.dataType ===
CLIN_ATTR_DATA_TYPE ||
axisSelection.dataType ===
- GENESET_DATA_TYPE
+ GENESET_DATA_TYPE ||
+ axisSelection.dataType ===
+ CUSTOM_ATTR_DATA_TYPE
}
/>
@@ -4125,6 +4270,8 @@ export default class PlotsTab extends React.Component {
undefined ||
axisSelection.dataType ===
CLIN_ATTR_DATA_TYPE ||
+ axisSelection.dataType ===
+ CUSTOM_ATTR_DATA_TYPE ||
!isGenericAssaySelected(
axisSelection
)
diff --git a/src/shared/components/plots/PlotsTabUtils.tsx b/src/shared/components/plots/PlotsTabUtils.tsx
index 97ba12da992..23af98bf8ef 100644
--- a/src/shared/components/plots/PlotsTabUtils.tsx
+++ b/src/shared/components/plots/PlotsTabUtils.tsx
@@ -85,6 +85,7 @@ import { AnnotatedNumericGeneMolecularData } from 'shared/model/AnnotatedNumeric
import { CustomDriverNumericGeneMolecularData } from 'shared/model/CustomDriverNumericGeneMolecularData';
export const CLIN_ATTR_DATA_TYPE = 'clinical_attribute';
+export const CUSTOM_ATTR_DATA_TYPE = 'custom_attribute';
export const GENESET_DATA_TYPE = 'GENESET_SCORE';
export const dataTypeToDisplayType: { [s: string]: string } = {
[AlterationTypeConstants.MUTATION_EXTENDED]: 'Mutation',
@@ -95,6 +96,7 @@ export const dataTypeToDisplayType: { [s: string]: string } = {
[AlterationTypeConstants.METHYLATION]: 'DNA Methylation',
[CLIN_ATTR_DATA_TYPE]: 'Clinical Attribute',
[GENESET_DATA_TYPE]: 'Gene Sets',
+ [CUSTOM_ATTR_DATA_TYPE]: 'Custom Data',
};
export const NO_GENE_OPTION = {
@@ -115,6 +117,7 @@ export const mutationTypeToDisplayName: {
export const dataTypeDisplayOrder = [
CLIN_ATTR_DATA_TYPE,
+ CUSTOM_ATTR_DATA_TYPE,
AlterationTypeConstants.MUTATION_EXTENDED,
AlterationTypeConstants.STRUCTURAL_VARIANT,
AlterationTypeConstants.COPY_NUMBER_ALTERATION,
@@ -1423,6 +1426,9 @@ export function makeAxisDataPromise(
clinicalAttributeIdToClinicalAttribute: MobxPromise<{
[clinicalAttributeId: string]: ClinicalAttribute;
}>,
+ customAttributeIdToClinicalAttribute: MobxPromise<{
+ [clinicalAttributeId: string]: ClinicalAttribute;
+ }>,
molecularProfileIdSuffixToMolecularProfiles: MobxPromise<{
[molecularProfileIdSuffix: string]: MolecularProfile[];
}>,
@@ -1495,6 +1501,21 @@ export function makeAxisDataPromise(
);
}
break;
+ case CUSTOM_ATTR_DATA_TYPE:
+ if (
+ selection.dataSourceId !== undefined &&
+ customAttributeIdToClinicalAttribute.isComplete
+ ) {
+ const attribute = customAttributeIdToClinicalAttribute.result![
+ selection.dataSourceId
+ ];
+ ret = makeAxisDataPromise_Clinical(
+ attribute,
+ clinicalDataCache,
+ patientKeyToSamples
+ );
+ }
+ break;
case GENESET_DATA_TYPE:
if (
selection.genesetId !== undefined &&
@@ -1553,6 +1574,9 @@ export function getAxisLabel(
clinicalAttributeIdToClinicalAttribute: {
[clinicalAttributeId: string]: ClinicalAttribute;
},
+ customAttributeIdToClinicalAttribute: {
+ [clinicalAttributeId: string]: ClinicalAttribute;
+ },
logScaleFunc: IAxisLogScaleParams | undefined
) {
let label = '';
@@ -1570,6 +1594,13 @@ export function getAxisLabel(
switch (selection.dataType) {
case NONE_SELECTED_OPTION_STRING_VALUE:
break;
+ case CUSTOM_ATTR_DATA_TYPE:
+ const customAttr =
+ customAttributeIdToClinicalAttribute[selection.dataSourceId!];
+ if (customAttr) {
+ label = customAttr.displayName;
+ }
+ break;
case CLIN_ATTR_DATA_TYPE:
const attribute =
clinicalAttributeIdToClinicalAttribute[selection.dataSourceId!];
@@ -1623,10 +1654,20 @@ export function getAxisDescription(
},
clinicalAttributeIdToClinicalAttribute: {
[clinicalAttributeId: string]: ClinicalAttribute;
+ },
+ customAttributeIdToClinicalAttribute: {
+ [clinicalAttributeId: string]: ClinicalAttribute;
}
) {
let ret = '';
switch (selection.dataType) {
+ case CUSTOM_ATTR_DATA_TYPE:
+ const customAttr =
+ customAttributeIdToClinicalAttribute[selection.dataSourceId!];
+ if (customAttr) {
+ ret = customAttr.description;
+ }
+ break;
case CLIN_ATTR_DATA_TYPE:
const attribute =
clinicalAttributeIdToClinicalAttribute[selection.dataSourceId!];
@@ -3169,6 +3210,7 @@ export function bothAxesNoMolecularProfile(
vertSelection: AxisMenuSelection
): boolean {
const noMolecularProfileDataTypes = [
+ CUSTOM_ATTR_DATA_TYPE,
CLIN_ATTR_DATA_TYPE,
NONE_SELECTED_OPTION_STRING_VALUE,
];
diff --git a/src/shared/components/resources/ResourceTab.tsx b/src/shared/components/resources/ResourceTab.tsx
index 2e75e311c83..1328d79b0f8 100644
--- a/src/shared/components/resources/ResourceTab.tsx
+++ b/src/shared/components/resources/ResourceTab.tsx
@@ -115,14 +115,25 @@ export default class ResourceTab extends React.Component<
return (
-
+
+
+
+ {this.currentResourceDatum.sampleId
+ ? this.currentResourceDatum.sampleId
+ : this.currentResourceDatum.patientId}
+ {this.currentResourceDatum.resourceDefinition
+ .description &&
+ ` | ${this.currentResourceDatum.resourceDefinition.description}`}
+
+
boolean;
openResource: (resource: ResourceData) => void;
+ sampleId?: React.ReactNode;
}
function icon(resource: ResourceData) {
@@ -45,7 +46,7 @@ function icon(resource: ResourceData) {
}
const ResourceTable = observer(
- ({ resources, isTabOpen, openResource }: IResourceTableProps) => {
+ ({ resources, isTabOpen, openResource, sampleId }: IResourceTableProps) => {
const resourceTable = useLocalObservable(() => ({
get data() {
return _.sortBy(resources, r => r.resourceDefinition.priority);
@@ -56,6 +57,7 @@ const ResourceTable = observer(