diff --git a/end-to-end-test/remote/specs/core/mutationTable.spec.js b/end-to-end-test/remote/specs/core/mutationTable.spec.js index 8b5bfe1b9e7..7fa93adeed2 100644 --- a/end-to-end-test/remote/specs/core/mutationTable.spec.js +++ b/end-to-end-test/remote/specs/core/mutationTable.spec.js @@ -168,20 +168,20 @@ describe('Mutation Table', function() { ); }); - it('should show the ClinVar id after adding the ClinVar column', () => { + it('should show the ClinVar interpretation after adding the ClinVar column', () => { // click on column button browser.click('button*=Columns'); // scroll down to activated "ClinVar" selection browser.scroll(1000, 1000); // click "clinvar" - browser.click('//*[text()="ClinVar ID"]'); + browser.click('//*[text()="ClinVar"]'); let res; browser.waitUntil( () => { res = executeInBrowser( () => $('[data-test="clinvar-data"]').length ); - return res == 25; + return res === 25; }, 60000, `Failed: There's 25 clinvar rows in table (${res} found)` diff --git a/packages/react-mutation-mapper/src/component/clinvar/ClinVarId.tsx b/packages/react-mutation-mapper/src/component/clinvar/ClinVarId.tsx deleted file mode 100644 index d0ea91479fc..00000000000 --- a/packages/react-mutation-mapper/src/component/clinvar/ClinVarId.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { DefaultTooltip } from 'cbioportal-frontend-commons'; -import { getClinVarId } from 'cbioportal-utils'; -import { MyVariantInfo } from 'genome-nexus-ts-api-client'; -import { makeObservable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; - -export type ClinVarIdProps = { - myVariantInfo?: MyVariantInfo; -}; - -@observer -export default class ClinVarId extends React.Component { - constructor(props: any) { - super(props); - makeObservable(this); - } - public render() { - const clinVarId = getClinVarId(this.props.myVariantInfo); - - if (clinVarId == null) { - return ( - Variant has no ClinVar data.} - > - -   - - - ); - } else { - const clinVarLink = `https://www.ncbi.nlm.nih.gov/clinvar/variation/${clinVarId}/`; - - return ( - Click to see variant on ClinVar website. - } - > - - {clinVarId} - - - ); - } - } -} diff --git a/packages/react-mutation-mapper/src/component/clinvar/ClinVarSummary.tsx b/packages/react-mutation-mapper/src/component/clinvar/ClinVarSummary.tsx new file mode 100644 index 00000000000..9df56f62e7c --- /dev/null +++ b/packages/react-mutation-mapper/src/component/clinvar/ClinVarSummary.tsx @@ -0,0 +1,146 @@ +import { + DefaultTooltip, + pluralize, + TruncatedText, +} from 'cbioportal-frontend-commons'; +import { getClinVarId } from 'cbioportal-utils'; +import { ClinVar, MyVariantInfo } from 'genome-nexus-ts-api-client'; +import _ from 'lodash'; +import * as React from 'react'; + +export type ClinVarSummaryProps = { + myVariantInfo?: MyVariantInfo; +}; + +export enum ClinVarOrigin { + GERMLINE = 'germline', + SOMATIC = 'somatic', +} + +export type RcvData = { + origin: string; + evidences: { + clinicalSignificance: string; + count: number; + }[]; +}; + +export type RcvCountMap = { + [origin: string]: { [clinicalSignificance: string]: number }; +}; + +export function getRcvCountMap(clinVar: ClinVar): RcvCountMap { + const filteredRcv = clinVar.rcv.filter( + rcv => + rcv.origin === ClinVarOrigin.GERMLINE || + rcv.origin === ClinVarOrigin.SOMATIC + ); + + // first map by origin, then count evidence number for each origin group + const rcvMap = _.groupBy(filteredRcv, d => d.origin); + + return _.mapValues(rcvMap, rcvs => + _.countBy(rcvs, rcv => rcv.clinicalSignificance) + ); +} + +export function getRcvData(rcvCountMap: RcvCountMap): RcvData[] { + return _.map(rcvCountMap, (clinicalSignificanceCountMap, origin) => ({ + origin, + evidences: _.map( + clinicalSignificanceCountMap, + (count, clinicalSignificance) => ({ clinicalSignificance, count }) + ), + })); +} + +export function formatClinicalSignificanceText(rcvData: RcvData[]) { + return _.uniq( + _.flatten( + rcvData.map(d => d.evidences.map(e => e.clinicalSignificance)) + ) + ).join(', '); +} + +export const ClinVarRcvInterpretation = (props: { + rcvData: RcvData[]; + className?: string; +}) => { + return ( +
+ {props.rcvData.map(d => ( +
+ {`${_.upperFirst(d.origin)}: `} + {d.evidences + .map( + e => + `${e.clinicalSignificance} (${ + e.count + } ${pluralize('evidence', e.count)})` + ) + .join(', ')} +
+ ))} +
+ ); +}; + +const NoClinVarData = () => { + return ( + Variant has no ClinVar data.} + > + +   + + + ); +}; + +const ClinVarSummary = (props: ClinVarSummaryProps) => { + const clinVar = props.myVariantInfo + ? props.myVariantInfo.clinVar + : undefined; + + if (!clinVar) { + return ; + } else { + const clinVarId = getClinVarId(props.myVariantInfo); + const clinVarLink = `https://www.ncbi.nlm.nih.gov/clinvar/variation/${clinVarId}/`; + const rcvData = getRcvData(getRcvCountMap(clinVar)); + + return ( + 0 + ? formatClinicalSignificanceText(rcvData) + : 'Unknown' + } + addTooltip="always" + tooltip={ +
+ +
+ (ClinVar ID:{' '} + + {clinVarId} + + ) +
+
+ } + /> + ); + } +}; + +export default ClinVarSummary; diff --git a/packages/react-mutation-mapper/src/component/column/ClinVar.tsx b/packages/react-mutation-mapper/src/component/column/ClinVar.tsx index c1a9f486731..bc2d9164411 100644 --- a/packages/react-mutation-mapper/src/component/column/ClinVar.tsx +++ b/packages/react-mutation-mapper/src/component/column/ClinVar.tsx @@ -1,11 +1,12 @@ -import autobind from 'autobind-decorator'; -import { getClinVarId } from 'cbioportal-utils'; import { MyVariantInfo } from 'genome-nexus-ts-api-client'; -import { observer } from 'mobx-react'; import * as React from 'react'; import { defaultSortMethod } from 'cbioportal-utils'; -import ClinVarId from '../clinvar/ClinVarId'; +import ClinVarSummary, { + formatClinicalSignificanceText, + getRcvCountMap, + getRcvData, +} from '../clinvar/ClinVarSummary'; import { MyVariantInfoProps, renderMyVariantInfoContent, @@ -17,28 +18,23 @@ export function download(myVariantInfo?: MyVariantInfo): string { return value ? value.toString() : ''; } -export function sortValue(myVariantInfo?: MyVariantInfo): number | null { - const id = getClinVarId(myVariantInfo); +export function sortValue(myVariantInfo?: MyVariantInfo): string | null { + const rcvData = + myVariantInfo && myVariantInfo.clinVar + ? getRcvData(getRcvCountMap(myVariantInfo.clinVar)) + : undefined; - return id ? parseInt(id, 10) : null; + return rcvData ? formatClinicalSignificanceText(rcvData) : null; } export function clinVarSortMethod(a: MyVariantInfo, b: MyVariantInfo) { return defaultSortMethod(sortValue(a), sortValue(b)); } -@observer -export default class ClinVar extends React.Component { - public static defaultProps: Partial = { - className: 'pull-right mr-1', - }; +const ClinVar = (props: MyVariantInfoProps) => { + return renderMyVariantInfoContent(props, (myVariantInfo: MyVariantInfo) => ( + + )); +}; - public render() { - return renderMyVariantInfoContent(this.props, this.getContent); - } - - @autobind - public getContent(myVariantInfo: MyVariantInfo) { - return ; - } -} +export default ClinVar; diff --git a/packages/react-mutation-mapper/src/component/column/MyVariantInfoHelper.tsx b/packages/react-mutation-mapper/src/component/column/MyVariantInfoHelper.tsx index 75f3d28bd6f..df1ac7f7c75 100644 --- a/packages/react-mutation-mapper/src/component/column/MyVariantInfoHelper.tsx +++ b/packages/react-mutation-mapper/src/component/column/MyVariantInfoHelper.tsx @@ -14,7 +14,7 @@ export type MyVariantInfoProps = { indexedVariantAnnotations?: RemoteData< { [genomicLocation: string]: VariantAnnotation } | undefined >; - indexedMyVariantInfoAnnotations: RemoteData< + indexedMyVariantInfoAnnotations?: RemoteData< { [genomicLocation: string]: MyVariantInfo } | undefined >; className?: string; @@ -28,7 +28,7 @@ export function renderMyVariantInfoContent( ) => JSX.Element ) { let content; - const status = props.indexedMyVariantInfoAnnotations.status; + const status = props.indexedMyVariantInfoAnnotations?.status || 'complete'; const variantAnnotation = props.indexedVariantAnnotations ? getVariantAnnotation( props.mutation, @@ -37,7 +37,7 @@ export function renderMyVariantInfoContent( : undefined; const myVariantInfo = getMyVariantInfoAnnotation( props.mutation, - props.indexedMyVariantInfoAnnotations.result + props.indexedMyVariantInfoAnnotations?.result ); if (status === 'pending') { diff --git a/packages/react-mutation-mapper/src/component/mutationTable/MutationColumnHelper.tsx b/packages/react-mutation-mapper/src/component/mutationTable/MutationColumnHelper.tsx index f9b4d4cf4de..1863f94b418 100644 --- a/packages/react-mutation-mapper/src/component/mutationTable/MutationColumnHelper.tsx +++ b/packages/react-mutation-mapper/src/component/mutationTable/MutationColumnHelper.tsx @@ -30,7 +30,7 @@ export enum MutationColumn { HGVSG = 'hgvsg', HGVSC = 'hgvsc', GNOMAD = 'gnomad', - CLINVAR = 'clinVarId', + CLINVAR = 'clinVar', DBSNP = 'dbsnp', SIGNAL = 'signal', } @@ -48,7 +48,7 @@ export enum MutationColumnName { HGVSG = 'HGVSg', HGVSC = 'HGVSc', GNOMAD = 'gnomAD', - CLINVAR = 'ClinVar ID', + CLINVAR = 'ClinVar', DBSNP = 'dbSNP', SIGNAL = 'SIGNAL', } @@ -181,7 +181,7 @@ export const MUTATION_COLUMN_HEADERS = { [MutationColumn.CLINVAR]: ( + {MutationColumnName.CLINVAR}{' '} diff --git a/packages/react-mutation-mapper/src/index.tsx b/packages/react-mutation-mapper/src/index.tsx index bf4db54fcbb..6c43064e6a4 100644 --- a/packages/react-mutation-mapper/src/index.tsx +++ b/packages/react-mutation-mapper/src/index.tsx @@ -3,7 +3,14 @@ export { download as civicDownload, sortValue as civicSortValue, } from './component/civic/Civic'; -export { default as ClinVarId } from './component/clinvar/ClinVarId'; +export { + default as ClinVarSummary, + ClinVarRcvInterpretation, + getRcvCountMap, + getRcvData, + RcvCountMap, + RcvData, +} from './component/clinvar/ClinVarSummary'; export { AnnotationProps, default as Annotation, diff --git a/packages/react-variant-view/src/component/pathogenicity/ClinVarInterpretation.tsx b/packages/react-variant-view/src/component/pathogenicity/ClinVarInterpretation.tsx index 86e74aa8945..9527e5d897e 100644 --- a/packages/react-variant-view/src/component/pathogenicity/ClinVarInterpretation.tsx +++ b/packages/react-variant-view/src/component/pathogenicity/ClinVarInterpretation.tsx @@ -1,25 +1,31 @@ -import _ from 'lodash'; import * as React from 'react'; - import { observer } from 'mobx-react'; import featureTableStyle from '../featureTable/FeatureTable.module.scss'; import { computed } from 'mobx'; import { ClinVar } from 'genome-nexus-ts-api-client'; import { Button } from 'react-bootstrap'; import { DefaultTooltip } from 'cbioportal-frontend-commons'; +import { + ClinVarRcvInterpretation, + getRcvCountMap, + getRcvData, +} from 'react-mutation-mapper'; -interface IClinVarInterpretationProps { +export interface IClinVarInterpretationProps { clinVar: ClinVar | undefined; } -enum ClinVarOrigin { - GERMLINE = 'germline', - SOMATIC = 'somatic', -} - -type rcvDisplayData = { - name: string; - content: string; +const ClinVarTooltip = () => { + return ( + ClinVar Interpretation} + > + + + ); }; @observer @@ -28,66 +34,34 @@ class ClinVarInterpretation extends React.Component< > { @computed get rcvCountMap() { if (this.props.clinVar) { - const clinVar = this.props.clinVar; - const filteredRcv = clinVar.rcv.filter( - rcv => - rcv.origin === ClinVarOrigin.GERMLINE || - rcv.origin === ClinVarOrigin.SOMATIC - ); - const rcvMap = _.groupBy(filteredRcv, d => d.origin); - const rcvCountMap = _.mapValues(rcvMap, rcvs => - _.countBy(rcvs, rcv => rcv.clinicalSignificance) - ); - return rcvCountMap; + return getRcvCountMap(this.props.clinVar); } + return undefined; } @computed get rcvDisplayData() { if (this.rcvCountMap) { - return _.map( - Object.entries(this.rcvCountMap), - ([origin, clinicalSignificanceCountMap]) => { - return { - name: origin, - content: _.join( - _.map( - Object.entries(clinicalSignificanceCountMap), - ([clinicalSignificance, count]) => { - return `${clinicalSignificance} (${count} evidences)`; - } - ), - ', ' - ), - }; - } - ); + return getRcvData(this.rcvCountMap); } return undefined; } @computed get clinVarContent() { - // first map by origin, then count evidence number for each origin group return this.rcvDisplayData ? (
- {this.clinVarTooltip()} -
-
- {_.map(this.rcvDisplayData, (d: rcvDisplayData) => { - return ( -
- {`${_.upperFirst(d.name)}: `} - {d.content} -
- ); - })} +
+
) : (
- {this.clinVarTooltip()} +
N/A
@@ -96,19 +70,6 @@ class ClinVarInterpretation extends React.Component< ); } - private clinVarTooltip() { - return ( - ClinVar Interpretation} - > - - - ); - } - public render() { return this.clinVarContent; } diff --git a/src/shared/components/mutationTable/MutationTable.tsx b/src/shared/components/mutationTable/MutationTable.tsx index f2260cb07a0..fec57f20a92 100644 --- a/src/shared/components/mutationTable/MutationTable.tsx +++ b/src/shared/components/mutationTable/MutationTable.tsx @@ -74,7 +74,6 @@ import { getDefaultASCNMethodColumnDefinition } from 'shared/components/mutation import { getDefaultCancerCellFractionColumnDefinition } from 'shared/components/mutationTable/column/cancerCellFraction/CancerCellFractionColumnFormatter'; import { getDefaultClonalColumnDefinition } from 'shared/components/mutationTable/column/clonal/ClonalColumnFormatter'; import { getDefaultExpectedAltCopiesColumnDefinition } from 'shared/components/mutationTable/column/expectedAltCopies/ExpectedAltCopiesColumnFormatter'; -import { hasASCNProperty } from 'shared/lib/MutationUtils'; export interface IMutationTableProps { studyIdToStudy?: { [studyId: string]: CancerStudy }; @@ -1038,7 +1037,7 @@ export default class MutationTable< }; this._columns[MutationTableColumnType.CLINVAR] = { - name: 'ClinVar ID', + name: 'ClinVar', render: (d: Mutation[]) => ClinVarColumnFormatter.renderFunction( d, @@ -1069,7 +1068,7 @@ export default class MutationTable< ), defaultSortDirection: 'desc', visible: false, - align: 'right', + align: 'left', }; this._columns[MutationTableColumnType.DBSNP] = { diff --git a/src/shared/components/mutationTable/column/ClinVarColumnFormatter.tsx b/src/shared/components/mutationTable/column/ClinVarColumnFormatter.tsx index c432f7cf69d..c9f9c5db29c 100644 --- a/src/shared/components/mutationTable/column/ClinVarColumnFormatter.tsx +++ b/src/shared/components/mutationTable/column/ClinVarColumnFormatter.tsx @@ -14,8 +14,6 @@ import { clinVarDownload, } from 'react-mutation-mapper'; -import generalStyles from './styles.module.scss'; - export default class ClinVarColumnFormatter { public static renderFunction( data: Mutation[], @@ -29,7 +27,6 @@ export default class ClinVarColumnFormatter { return (
- ): number | null { + ): string | null { const myVariantInfo = ClinVarColumnFormatter.getData( data, indexedMyVariantInfoAnnotations