import * as React from 'react'; import { inject, observer } from 'mobx-react'; import { action, computed, observable, makeObservable } from 'mobx'; import autobind from 'autobind-decorator'; import { Collapse } from 'react-collapse'; import { ControlLabel, FormControl, FormGroup, ButtonGroup, Radio, } from 'react-bootstrap'; import { PageLayout } from 'shared/components/PageLayout/PageLayout'; import Helmet from 'react-helmet'; import Loader from 'shared/components/loadingIndicator/LoadingIndicator'; import { MSKTab, MSKTabs } from 'shared/components/MSKTabs/MSKTabs'; import { convertToMutationMapperProps } from 'shared/components/mutationMapper/MutationMapperServerConfig'; import MutationMapperUserSelectionStore from 'shared/components/mutationMapper/MutationMapperUserSelectionStore'; import { parseInput } from 'shared/lib/MutationInputParser'; import StandaloneMutationMapper from './StandaloneMutationMapper'; import MutationMapperToolStore from './MutationMapperToolStore'; import { getMutationAlignerUrlTemplate, getOncoKbApiUrl, } from 'shared/api/urls'; import { getBrowserWindow } from 'cbioportal-frontend-commons'; import { REFERENCE_GENOME } from 'shared/lib/referenceGenomeUtils'; import { getServerConfig } from 'config/config'; import { updateOncoKbIconStyle } from 'shared/lib/AnnotationColumnUtils'; interface IMutationMapperToolProps { routing: any; } const ONCOKB_URL = 'https://www.oncokb.org/'; const CIVIC_URL = 'https://civicdb.org/'; const CANCER_HOTSPOTS_URL = 'https://www.cancerhotspots.org/'; const MY_CANCER_GENOME_URL = 'https://www.mycancergenome.org/'; @inject('routing') @observer export default class MutationMapperTool extends React.Component< IMutationMapperToolProps, {} > { private userSelectionStore: MutationMapperUserSelectionStore; @observable.ref standaloneMutationMapperGeneTab: | string | undefined = undefined; @observable dataFormatCollapsed = true; @observable.ref inputText: string | undefined = undefined; @observable inputControlsVisible = true; @observable.ref inputFileContent: string | undefined = undefined; @observable showIncorrectInput = false; @observable.ref lastParsedInputContent: string | undefined = undefined; @observable referenceGenomeSelection: string = REFERENCE_GENOME.grch37.NCBI; private store: MutationMapperToolStore = new MutationMapperToolStore(); constructor(props: IMutationMapperToolProps) { super(props); makeObservable(this); this.userSelectionStore = new MutationMapperUserSelectionStore(); // set genomenexus url to grch38 instance if "show_mutation_mapper_tool_grch38" is true and choose "GRCh38", otherwise use default url if ( getServerConfig().show_mutation_mapper_tool_grch38 && getBrowserWindow().localStorage.getItem('referenceGenomeId') === REFERENCE_GENOME.grch38.NCBI ) { this.store.setGenomeNexusUrl( getServerConfig().genomenexus_url_grch38! ); this.referenceGenomeSelection = REFERENCE_GENOME.grch38.NCBI; } this.handleTabChange.bind(this); } public render() { return ( {'cBioPortal for Cancer Genomics::MutationMapper'}

MutationMapper

{' '} interprets mutations with protein annotations
{this.input()}
Annotating mutations with{' '} Genome Nexus } /> {this.store.mutationsNotAnnotated.length > 0 && this.inputParseWarning()} {this.shouldShowGrch38Warning && this.grch38Warning} {this.store.mutationData && this.mainTabs()}
); } @computed get activeTabId(): string | undefined { // use routing if available, if not fall back to the observable variable return this.props.routing ? this.props.routing.query.standaloneMutationMapperGeneTab : this.standaloneMutationMapperGeneTab; } @computed get inputContent(): string { // input field has priority over input file const content = this.inputText || this.inputFileContent; return content ? content.trim() : ''; } @autobind toggleIncorrectInput() { this.showIncorrectInput = !this.showIncorrectInput; } protected inputParseWarning() { if ( this.store.hugoGeneSymbols.isComplete && this.store.hugoGeneSymbols.result && this.store.hugoGeneSymbols.result.length > 0 && this.lastParsedInputContent && this.store.mutationsNotAnnotated.length > 0 ) { return (
Failed to annotate {this.store.mutationsNotAnnotated.length}{' '} mutation {this.store.mutationsNotAnnotated.length > 1 ? 's' : ''}   {this.showIncorrectInput && this.lastParsedInputContent && this.store.mutationsNotAnnotated.map( failedAnnotation => (
Line {failedAnnotation.lineNumber}:{' '} {this.lastParsedInputContent && this.lastParsedInputContent.split('\n')[ failedAnnotation.lineNumber - 1 ]}
) )} ( {this.showIncorrectInput ? 'Hide' : 'Show'} )
); } else { return null; } } protected mainTabs() { if ( this.store.hugoGeneSymbols.isComplete && this.store.hugoGeneSymbols.result && this.store.hugoGeneSymbols.result.length > 0 ) { const activeTabId = this.activeTabId || this.store.hugoGeneSymbols.result[0]; return (
{this.store.mutationMapperStores.isComplete && ( this.handleTabChange(id) } className="pillTabs" arrowStyle={{ 'line-height': 0.8 }} tabButtonStyle="pills" unmountOnHide={true} > {this.generateTabs( this.store.hugoGeneSymbols.result )} )}
); } else if ( this.store.hugoGeneSymbols.isComplete && !this.inputControlsVisible ) { return (
Error processing the input. Please review your input and try again.
); } else if (this.store.criticalErrors.length > 0) { return (
Critical annotation error. Annotation services might be temporarily down, or your input format might be invalid.
); } else { return null; } } protected input() { if (this.inputControlsVisible) { return (
{this.referenceGenomeSelector}

Please input tab-delimited or{' '} space-delimited mutation data. (Load example data: {' '} Genomic Changes , {' '} Protein Changes , {' '} Genomic and Protein Changes )

{this.dataFormatToggler()} Copy and paste your own mutation data Upload your own mutation file
); } else { return (
); } } protected dataFormatToggler() { return (

Data Format {this.dataFormatCollapsed ? ( ) : ( )}
{this.essentialColumnsInfo()}

List of valid input headers:

{this.inputFormatDesc()}

); } protected essentialColumnsInfo() { return (

You can either copy and paste your input into the text field below or select a file with mutation data for upload.

Mutation files should be tab delimited, and should at least have the genomic location headers in the first line for a successful annotation. Note that all variants have to be reported for genome build GRCh37 (hg19).

  • Chromosome
  • Start_Position
  • End_Position
  • Reference_Allele
  • Variant_Allele

If your mutation input doesn't contain the genomic location headers, but you still want to visualize your data, your input should at least have the following headers:

  • Hugo_Symbol
  • Protein_Change
); } protected inputFormatDesc() { // TODO add & support more columns return (
Column Header Description Example
Hugo_Symbol HUGO symbol for the gene TP53
Protein_Change Amino acid change V600E
Sample_ID Tumor sample ID TCGA-B5-A11E
Mutation_Type Translational effect of variant allele Missense_Mutation, Nonsense_Mutation, etc.
Chromosome Chromosome number X, Y, M, 1, 2, etc.
Start_Position Lowest numeric position of the reported variant on the genomic reference sequence 666
End_Position Highest numeric position of the reported variant on the genomic reference sequence 667
Reference_Allele The plus strand reference allele at this position A
Variant_Allele Tumor sequencing (discovery) allele C
Validation_Status Second pass results from orthogonal technology Valid
Mutation_Status Mutation status Somatic, Germline, etc.
Center Center/Institute reporting the variant mskcc.org
); } @computed get referenceGenomeSelector() { // show reference genome selector if "show_mutation_mapper_tool_grch38" is true if (getServerConfig().show_mutation_mapper_tool_grch38) { return (
Select Reference Genome:{' '} { this.handleReferenceGenomeSelectionChange( REFERENCE_GENOME.grch37.NCBI ); }} inline data-value={REFERENCE_GENOME.grch37.NCBI} data-test="MutationMapperToolGRCh37Button" > {REFERENCE_GENOME.grch37.NCBI} { this.handleReferenceGenomeSelectionChange( REFERENCE_GENOME.grch38.NCBI ); }} inline data-value={REFERENCE_GENOME.grch38.NCBI} data-test="MutationMapperToolGRCh38Button" > {REFERENCE_GENOME.grch38.NCBI}{' '} Beta!
); } else { return (
The annotations are based on genome build GRCh37 (hg19).
); } } protected generateTabs(genes: string[]) { const tabs: JSX.Element[] = []; genes.forEach((gene: string) => { const mutationMapperStore = this.store.getMutationMapperStore(gene); if (mutationMapperStore) { tabs.push( ); } }); return tabs; } @action.bound protected handleFileSelect(e: any) { const reader = new FileReader(); reader.addEventListener('loadend', this.fileLoadEndHandler); if (e.target.files && e.target.files[0]) { // this will fire a "loadend" event reader.readAsText(e.target.files[0]); } else { // reset input file content if no file is selected this.inputFileContent = ''; } } @action.bound protected fileLoadEndHandler(e: any) { if (e.srcElement && e.srcElement.result) { // update input file content this.inputFileContent = e.srcElement.result; // reset input text to avoid confusion this.inputText = ''; } } @action.bound protected handleInputChange(e: any) { this.inputText = e.target.value; } @action.bound protected handleTabChange(id: string | undefined) { // update the hash if routing exits if (this.props.routing) { this.props.routing.updateRoute({ standaloneMutationMapperGeneTab: id, }); } // update the observable if no routing else { this.standaloneMutationMapperGeneTab = id; } } @action.bound protected handleModifyInput() { // clear previous critical errors this.store.clearCriticalErrors(); // show input controls this.inputControlsVisible = true; } @action.bound protected handleVisualize() { // clear previous critical errors this.store.clearCriticalErrors(); this.store.mutationData = parseInput(this.inputContent); // save to keep input content after user makes changes this.lastParsedInputContent = this.inputContent; // hide input controls this.inputControlsVisible = false; } @action.bound protected handleDataFormatToggle() { this.dataFormatCollapsed = !this.dataFormatCollapsed; } @action.bound protected handleLoadExamplePartiallyAnnotated() { if (this.referenceGenomeSelection === REFERENCE_GENOME.grch37.NCBI) { this.inputText = require('raw-loader!./resources/standaloneMutationDataExampleGrch37.txt'); } else { this.inputText = require('raw-loader!./resources/standaloneMutationDataExampleGrch38.txt'); } } @action.bound protected handleLoadExampleGenomicCoordinates() { if (this.referenceGenomeSelection === REFERENCE_GENOME.grch37.NCBI) { this.inputText = require('raw-loader!./resources/standaloneMutationDataExampleWithGenomicCoordinatesOnlyGrch37.txt'); } else { this.inputText = require('raw-loader!./resources/standaloneMutationDataExampleWithGenomicCoordinatesOnlyGrch38.txt'); } } @action.bound protected handleLoadExampleGeneAndProteinChange() { this.inputText = require('raw-loader!./resources/standaloneMutationDataExampleWithGeneAndProteinChangeOnly.txt'); } @action.bound protected handleReferenceGenomeSelectionChange(selection: string) { // store the reference genome selection to localStorage getBrowserWindow().localStorage.setItem('referenceGenomeId', selection); // reload the page to initialize genome nexus api instance with correct url getBrowserWindow().location.reload(); } @action.bound protected handleOncoKbIconToggle(mergeIcons: boolean) { this.userSelectionStore.mergeOncoKbIcons = mergeIcons; updateOncoKbIconStyle({ mergeIcons }); } @computed get grch38Warning() { return (
Genome build GRCh38 is currently in beta. Several annotation sources displayed on the page might not have official GRCh38 support i.e.  OncoKB™,  CIViC,  Cancer Hotspots,  My Cancer Genome and  3D structures. Although most of the time the canonical transcript for a gene will be the same between GRCh37 and GRCh38 be sure to look at the results from these annotation sources carefully.
); } @computed get isGrch38() { return this.referenceGenomeSelection === REFERENCE_GENOME.grch38.NCBI; } @computed get ensemblLink() { return this.isGrch38 ? getServerConfig().ensembl_transcript_grch38_url : getServerConfig().ensembl_transcript_url; } @computed get shouldShowGrch38Warning() { return ( this.isGrch38 && !this.inputControlsVisible && this.store.mutations.isComplete ); } }