import * as React from 'react'; import { If, Then, Else } from 'react-if'; import { observer } from 'mobx-react'; import _ from 'lodash'; import { IClinicalGroupMatch, IGenomicGroupMatch, IGenomicMatch, IDetailedTrialMatch, IArmMatch, IGenomicMatchType, } from '../../../shared/model/MatchMiner'; import styles from './style/trialMatch.module.scss'; import { action, computed, makeObservable, observable } from 'mobx'; import LazyMobXTable from '../../../shared/components/lazyMobXTable/LazyMobXTable'; import SampleManager from '../SampleManager'; import { DefaultTooltip, placeArrowBottomLeft, } from 'cbioportal-frontend-commons'; import { getAgeRangeDisplay } from './TrialMatchTableUtils'; import TrialMatchFeedback from './TrialMatchFeedback'; import { getServerConfig } from 'config/config'; import { Button } from 'react-bootstrap'; export type ITrialMatchProps = { sampleManager: SampleManager | null; detailedTrialMatches: IDetailedTrialMatch[]; containerWidth: number; }; export type ISelectedTrialFeedbackFormData = { nctId: string; protocolNo: string; }; enum ColumnKey { TITLE = 'Title', MATCHING_CRITERIA = 'Matching Criteria', STATUS = 'Status', } enum ColumnWidth { STATUS = 140, } enum AlterationType { MUTATION = 'Mutation', CNA = 'Copy Number Alteration', MSI = 'Microsatellite Instability', WILDTYPE = 'Wildtype', } class TrialMatchTableComponent extends LazyMobXTable {} @observer export default class TrialMatchTable extends React.Component { constructor(props: ITrialMatchProps) { super(props); makeObservable(this); } @computed get columnWidths() { return { [ColumnKey.STATUS]: ColumnWidth.STATUS, [ColumnKey.TITLE]: 0.35 * (this.props.containerWidth - ColumnWidth.STATUS), [ColumnKey.MATCHING_CRITERIA]: 0.65 * (this.props.containerWidth - ColumnWidth.STATUS), }; } @observable selectedTrialFeedbackFormData: | ISelectedTrialFeedbackFormData | undefined; @observable showGeneralFeedback = false; private _columns = [ { name: ColumnKey.TITLE, render: (trial: IDetailedTrialMatch) => (
0}>
{trial.protocolNo}
0}>
{trial.nctId}
{trial.shortTitle}
{trial.principalInvestigator && (
{trial.principalInvestigator.full_name} {trial.principalInvestigator.full_name}
)}
), width: this.columnWidths[ColumnKey.TITLE], }, { name: ColumnKey.MATCHING_CRITERIA, render: (trial: IDetailedTrialMatch) => (
{trial.matches.map((armMatch: IArmMatch, index: number) => (
{armMatch.matches.map( ( clinicalGroupMatch: IClinicalGroupMatch, cgIndex: number ) => (
{clinicalGroupMatch.matches && this.getGenomicMatch( clinicalGroupMatch.matches )} {clinicalGroupMatch.notMatches && this.getGenomicNotMatch( clinicalGroupMatch.notMatches )}
{this.getClinicalMatch( clinicalGroupMatch )}
) )}
Matched Samples:{' '} {this.getSampleIdIcons( armMatch.sampleIds )}
Arm: {armMatch.armDescription}
0}>
drug icon {armMatch.drugs .map( ( drugCombination: string[] ) => drugCombination.join( ' + ' ) ) .join(', ')}

))}
), width: this.columnWidths[ColumnKey.MATCHING_CRITERIA], }, { name: ColumnKey.STATUS, render: (trial: IDetailedTrialMatch) => (
{trial.status}
), sortBy: (trial: IDetailedTrialMatch) => trial.status, width: this.columnWidths[ColumnKey.STATUS], }, ]; public getSampleIdIcons(sampleIds: string[]) { let sortedSampleIds = sampleIds; if (sampleIds.length > 1) { const sampleOrder = this.props.sampleManager!.getSampleIdsInOrder(); sortedSampleIds = sampleOrder.filter((sampleId: string) => sampleIds.includes(sampleId) ); } return ( {sortedSampleIds.map((sampleId: string) => ( {this.props.sampleManager!.getComponentForSample( sampleId, 1, '' )} ))} ); } public getClinicalMatch(clinicalGroupMatch: IClinicalGroupMatch) { return (
{getAgeRangeDisplay(clinicalGroupMatch.trialAgeNumerical)} {clinicalGroupMatch.trialOncotreePrimaryDiagnosis.positive.join( ', ' )} {clinicalGroupMatch.trialOncotreePrimaryDiagnosis.negative .length > 0 && ( except {clinicalGroupMatch.trialOncotreePrimaryDiagnosis.negative .join(', ') .replace(/,(?!.*,)/gim, ' and')} {clinicalGroupMatch .trialOncotreePrimaryDiagnosis .negative.length + ` cancer types`} )}
); } @action public openCloseFeedbackForm(data?: ISelectedTrialFeedbackFormData) { this.selectedTrialFeedbackFormData = data; } public getGenomicMatch(matches: IGenomicMatchType) { return ( {matches.MUTATION.map( (genomicGroupMatch: IGenomicGroupMatch) => (
{ genomicGroupMatch.patientGenomic! .trueHugoSymbol }{' '} {genomicGroupMatch.patientGenomic!.trueProteinChange.join( ', ' )}
) )} {matches.MSI.length > 0 && (
Tumor is MSI-H
)} {matches.CNA.map((genomicGroupMatch: IGenomicGroupMatch) => (
{this.getGenomicAlteration( genomicGroupMatch.genomicAlteration )}
))} {matches.WILDTYPE.map( (genomicGroupMatch: IGenomicGroupMatch) => (
{this.getGenomicAlteration( genomicGroupMatch.genomicAlteration )}
) )}
); } public getGenomicNotMatch(notMatches: IGenomicMatchType) { let mutationAndCnagenemicAlterations: string[] = []; if (notMatches.MUTATION.length > 0) { mutationAndCnagenemicAlterations = notMatches.MUTATION[0].genomicAlteration; } if (notMatches.CNA.length > 0) { mutationAndCnagenemicAlterations = mutationAndCnagenemicAlterations.concat( notMatches.CNA[0].genomicAlteration ); } return ( {mutationAndCnagenemicAlterations.length > 0 && (
{this.getDescriptionForNotMatches( mutationAndCnagenemicAlterations, 3, AlterationType.MUTATION )}
)} {notMatches.MSI.length > 0 && (
Tumor is not MSI-H
)} {notMatches.WILDTYPE.length > 0 && (
{this.getDescriptionForNotMatches( notMatches.WILDTYPE[0].genomicAlteration, 3, AlterationType.WILDTYPE )}
)}
); } public getGenomicAlteration(data: string[]) { const filteredData = data.map((e: string) => e.split(' ')); return (
{filteredData.map((e: string[]) => (
{e[0]} {e[1]}
))}
); } public tooltipGenomicContent(data: string[]) { return (
Genomic selection{' '} {data.length > 1 ? 'criteria' : 'criterion'} specified in the trial:
{data.map((e: string) => (
Not {e.replace(/!/g, '')} {e}
))}
); } public tooltipClinicalContent(data: string[]) { return (
    {data.map((cancerType: string) => (
  • {cancerType}
  • ))}
); } public getDescriptionForNotMatches( genomicAlteration: string[], threshold: number, type: string ) { const hugoSymbolSet = new Set( [...genomicAlteration].map((s: string) => s.split(' ')[0]) ); let genomicAlterationContent = ''; if (type === AlterationType.MUTATION) { if (hugoSymbolSet.size === 1) { genomicAlterationContent = [...hugoSymbolSet].join(', ') + ' ' + [...genomicAlteration] .map((s: string) => s.split(' ')[1].replace(/!/g, '')) .join(', '); return `Negative for ${genomicAlterationContent}`; } else if (hugoSymbolSet.size <= threshold) { genomicAlterationContent = [...hugoSymbolSet].join(', '); } else { genomicAlterationContent = `${hugoSymbolSet.size} genes`; } return `Negative for alterations in ${genomicAlterationContent}`; } else if (type === AlterationType.WILDTYPE) { if (hugoSymbolSet.size <= threshold) { genomicAlterationContent = [...hugoSymbolSet].join(', '); } else { genomicAlterationContent = `${hugoSymbolSet.size} genes`; } return `Tumor doesn't have ${genomicAlterationContent} defined by the trial`; } return ''; } render() { return (

Curated genomic and clinical criteria from open clinical trials at Memorial Sloan Kettering. Please{' '} contact us or submit{' '} (this.showGeneralFeedback = true)}> feedback form {' '} if you have any questions.

{!_.isUndefined(this.showGeneralFeedback) && ( (this.showGeneralFeedback = false)} title="OncoKB Matched Trials General Feedback" userEmailAddress={getServerConfig().user_email_address} /> )} {this.selectedTrialFeedbackFormData && ( this.openCloseFeedbackForm()} isTrialFeedback={true} title="OncoKB Matched Trial Feedback" userEmailAddress={getServerConfig().user_email_address} /> )}
Powered by{' '} OncoKB {' '} &{' '} MatchMiner
); } }