Unverified Commit 5999a8f4 authored by Ridwan Amode's avatar Ridwan Amode Committed by GitHub
Browse files

Add sequence view to genome browser drawer (#725)

parent b683a879
Pipeline #265684 passed with stages
in 4 minutes and 48 seconds
......@@ -26,7 +26,7 @@ import {
DefaultTranscriptListItemProps
} from './DefaultTranscriptListItem';
import { createTranscript } from 'tests/fixtures/entity-viewer/transcript';
import { createProteinCodingTranscript } from 'tests/fixtures/entity-viewer/transcript';
import {
createGene,
createRulerTicks
......@@ -84,7 +84,7 @@ describe('<DefaultTranscriptListItem />', () => {
const defaultProps = {
transcriptPosition: 1,
gene: createGene(),
transcript: createTranscript(),
transcript: createProteinCodingTranscript(),
rulerTicks: createRulerTicks(),
expandTranscript: false,
expandDownload: false,
......
......@@ -29,7 +29,7 @@ import {
import { createGene } from 'tests/fixtures/entity-viewer/gene';
import {
createTranscript,
createProteinCodingTranscript,
createTranscriptMetadata
} from 'tests/fixtures/entity-viewer/transcript';
import { createExternalReference } from 'tests/fixtures/entity-viewer/external-reference';
......@@ -46,7 +46,7 @@ jest.mock('src/shared/components/instant-download', () => ({
)
}));
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
const gene = createGene({ transcripts: [transcript] });
const expandDownload = false;
const expandMoreInfo = false;
......@@ -60,7 +60,7 @@ const createGencodeBasicTranscript = () => {
}
});
const transcript = createTranscript({ metadata });
const transcript = createProteinCodingTranscript({ metadata });
return transcript;
};
......@@ -77,7 +77,7 @@ const createMANETranscript = () => {
}
});
const transcript = createTranscript({ metadata });
const transcript = createProteinCodingTranscript({ metadata });
return transcript;
};
......@@ -89,7 +89,7 @@ const createCCDSXrefTranscript = () => {
url: faker.internet.url()
}
};
const transcript = createTranscript({
const transcript = createProteinCodingTranscript({
external_references: [createExternalReference(xref)]
});
return transcript;
......
......@@ -23,7 +23,10 @@ import thunk from 'redux-thunk';
import set from 'lodash/fp/set';
import { Status } from 'src/shared/types/status';
import { createTranscript } from 'tests/fixtures/entity-viewer/transcript';
import {
createProteinCodingTranscript,
createNonCodingTranscript
} from 'tests/fixtures/entity-viewer/transcript';
import TranscriptsFilter from './TranscriptsFilter';
jest.mock(
......@@ -64,13 +67,6 @@ const mockState = {
const mockStore = configureMockStore([thunk]);
const createProteinCodingTranscript = () => createTranscript();
const createNonCodingTranscript = () => {
const transcript = createTranscript();
transcript.product_generating_contexts = [];
return transcript;
};
const proteinCodingTranscript1 = createProteinCodingTranscript();
const proteinCodingTranscript2 = createProteinCodingTranscript();
const nonCodingTranscript1 = createNonCodingTranscript();
......
......@@ -17,12 +17,12 @@
import React from 'react';
import { render } from '@testing-library/react';
import { createTranscript } from 'tests/fixtures/entity-viewer/transcript';
import { createProteinCodingTranscript } from 'tests/fixtures/entity-viewer/transcript';
import UnsplicedTranscript from './UnsplicedTranscript';
const minimalProps = {
transcript: createTranscript(),
transcript: createProteinCodingTranscript(),
width: 600
};
......
......@@ -16,7 +16,7 @@
import { filterTranscripts } from '../transcripts-filter';
import { createTranscript } from 'tests/fixtures/entity-viewer/transcript';
import { createProteinCodingTranscript } from 'tests/fixtures/entity-viewer/transcript';
/* Creating filters with different filter set to true/false */
const proteinCodingFilters = {
......@@ -42,20 +42,20 @@ const proteinCodingFilters = {
}
};
const createProteinCodingTranscript = () => {
const transcript = createTranscript();
const createProteinTranscript = () => {
const transcript = createProteinCodingTranscript();
transcript.metadata.biotype.value = 'protein_coding';
return transcript;
};
const createProcessedTranscript = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.metadata.biotype.value = 'processed_transcript';
return transcript;
};
const createTSLTranscript = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.metadata.tsl = {
label: 'TSL1',
value: 'tsl1',
......@@ -65,12 +65,12 @@ const createTSLTranscript = () => {
};
const createNonsenseMediatedDecayTranscript = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.metadata.biotype.value = 'nonsense_mediated_decay';
return transcript;
};
const ProteinCodingTranscript = createProteinCodingTranscript();
const ProteinCodingTranscript = createProteinTranscript();
const ProcessedTranscript = createProcessedTranscript();
const TSLTranscript = createTSLTranscript();
const NonsenseMediatedDecayTranscript = createNonsenseMediatedDecayTranscript();
......
......@@ -25,29 +25,29 @@ import {
} from '../transcripts-sorter';
import {
createTranscript,
createProteinCodingTranscript,
createTranscriptMetadata
} from 'tests/fixtures/entity-viewer/transcript';
/* Creating dummy transcritps with different protein coding and non coding length to test default sort*/
const createLongProteinCodingTranscript = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.slice.location.length = 100_000;
return transcript;
};
const createShortProteinCodingTranscript = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.slice.location.length = 10_000;
return transcript;
};
const createLongNonCodingTranscript = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.slice.location.length = 150_000;
transcript.product_generating_contexts = [];
return transcript;
};
const createShortNonCodingTranscript = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.slice.location.length = 5_000;
transcript.product_generating_contexts = [];
return transcript;
......@@ -55,7 +55,7 @@ const createShortNonCodingTranscript = () => {
/* Creating dummy transcritps with different spliced length */
const createTranscriptWithGreatestSplicedLength = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
const splicedExon = transcript.spliced_exons[0];
transcript.stable_id = 'transcript_with_greatest_spliced_length';
splicedExon.exon.slice.location.length = 15_000;
......@@ -63,7 +63,7 @@ const createTranscriptWithGreatestSplicedLength = () => {
return transcript;
};
const createTranscriptWithMediumSplicedLength = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.stable_id = 'transcript_with_medium_spliced_length';
const splicedExon = transcript.spliced_exons[0];
splicedExon.exon.slice.location.length = 10_000;
......@@ -71,7 +71,7 @@ const createTranscriptWithMediumSplicedLength = () => {
return transcript;
};
const createTranscriptWithSmallestSplicedLength = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.stable_id = 'transcript_with_smallest_spliced_length';
const splicedExon = transcript.spliced_exons[0];
splicedExon.exon.slice.location.length = 5_000;
......@@ -81,7 +81,7 @@ const createTranscriptWithSmallestSplicedLength = () => {
/* Creating dummy transcritps with different numbers of Exons */
const createTranscriptWithGreatestExons = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
const splicedExon = transcript.spliced_exons[0];
transcript.stable_id = 'transcript_with_greatest_exons';
transcript.spliced_exons = [
......@@ -93,7 +93,7 @@ const createTranscriptWithGreatestExons = () => {
return transcript;
};
const createTranscriptWithMediumExons = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.stable_id = 'transcript_with_medium_exons';
const splicedExon = transcript.spliced_exons[0];
splicedExon.exon.slice.location.length = 10_000;
......@@ -101,7 +101,7 @@ const createTranscriptWithMediumExons = () => {
return transcript;
};
const createTranscriptWithSmallestExons = () => {
const transcript = createTranscript();
const transcript = createProteinCodingTranscript();
transcript.stable_id = 'transcript_with_smallest_exons';
const splicedExon = transcript.spliced_exons[0];
transcript.spliced_exons = [splicedExon, splicedExon];
......@@ -131,7 +131,7 @@ const createMANETranscript = () => {
}
});
const transcript = createTranscript({ metadata });
const transcript = createProteinCodingTranscript({ metadata });
return transcript;
};
......@@ -152,7 +152,7 @@ const createOtherMANETranscript = () => {
}
}
});
const transcript = createTranscript({ metadata });
const transcript = createProteinCodingTranscript({ metadata });
return transcript;
};
......
/**
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import noop from 'lodash/noop';
import * as urlFor from 'src/shared/helpers/urlHelper';
import RadioGroup, {
RadioOptions
} from 'src/shared/components/radio-group/RadioGroup';
import { GeneSummaryQueryResult } from 'src/content/app/genome-browser/state/api/queries/geneSummaryQuery';
import styles from './SequenceView.scss';
type Gene = {
slice: GeneSummaryQueryResult['gene']['slice'];
};
type Props = {
gene: Gene;
};
export const GeneSequenceView = (props: Props) => {
const sequenceType = 'genomicSequence';
const { checksum } = props.gene.slice.region.sequence;
const { start, end } = props.gene.slice.location;
const sequenceURL = urlFor.refget({ checksum, start, end });
const radioOptions: RadioOptions = [
{
value: 'genomicSequence',
label: 'Genomic sequence'
}
];
return (
<div className={styles.layout}>
<div>
<div>XXXX bp</div>
<div className={styles.sequenceWrapper}>
Fetching sequence for {sequenceType} : {sequenceURL}
</div>
</div>
<div>
<div>blast control</div>
<div className={styles.selectionWrapper}>
<RadioGroup
options={radioOptions}
onChange={noop}
selectedOption={sequenceType}
/>
<div className={styles.reverseWrapper}>
Reverse complement checkbox
</div>
</div>
</div>
</div>
);
};
export default GeneSequenceView;
.layout {
display: grid;
align-items: start;
grid-template-columns: 400px 1fr;
grid-column-gap: 24px;
grid-template-rows: 20px minmax(200px, 400px);
grid-row-gap: 17px;
}
.selectionWrapper {
margin-top: 50px;
}
.sequenceWrapper {
margin-top: 10px;
}
.reverseWrapper {
margin-top: 43px;
}
/**
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { render } from '@testing-library/react';
import {
createProteinCodingTranscript,
createNonCodingTranscript
} from 'tests/fixtures/entity-viewer/transcript';
import TranscriptSequenceView from './TranscriptSequenceView';
describe('<TranscriptSequenceView />', () => {
const proteinCodingTranscript = createProteinCodingTranscript();
it('displays correct list of sequence options for a protein-coding transcript', () => {
const { container } = render(
<TranscriptSequenceView transcript={proteinCodingTranscript} />
);
const renderedLabels = [
...container.querySelectorAll('.radioGroup .label')
].map((el) => el.innerHTML);
expect(renderedLabels).toEqual([
'Genomic sequence',
'cDNA',
'CDS',
'Protein sequence'
]);
});
it('displays correct list of sequence options for a non-coding transcript', () => {
const nonCodingTranscript = createNonCodingTranscript();
const { container } = render(
<TranscriptSequenceView transcript={nonCodingTranscript} />
);
const renderedLabels = [
...container.querySelectorAll('.radioGroup .label')
].map((el) => el.innerHTML);
expect(renderedLabels).toEqual(['Genomic sequence', 'cDNA']);
});
});
/**
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useState } from 'react';
import * as urlFor from 'src/shared/helpers/urlHelper';
import { isProteinCodingTranscript } from 'src/content/app/entity-viewer/shared/helpers/entity-helpers';
import RadioGroup, {
OptionValue,
RadioOptions
} from 'src/shared/components/radio-group/RadioGroup';
import { TranscriptSummaryQueryResult } from 'src/content/app/genome-browser/state/api/queries/transcriptSummaryQuery';
import styles from './SequenceView.scss';
type Transcript = {
slice: TranscriptSummaryQueryResult['transcript']['slice'];
product_generating_contexts: TranscriptSummaryQueryResult['transcript']['product_generating_contexts'];
};
type Props = {
transcript: Transcript;
};
const TranscriptSequenceView = (props: Props) => {
const [sequenceType, setSequenceType] = useState('genomicSequence');
const { checksum } = props.transcript.slice.region.sequence;
const { start, end } = props.transcript.slice.location;
const genomicURL = urlFor.refget({ checksum, start, end });
const [sequenceURL, setSequenceURL] = useState(genomicURL);
let radioOptions: RadioOptions = [
{
value: 'genomicSequence',
label: 'Genomic sequence'
},
{
value: 'cdna',
label: 'cDNA'
},
{
value: 'cds',
label: 'CDS'
},
{
value: 'proteinSequence',
label: 'Protein sequence'
}
];
const handleRadioChange = (value: OptionValue) => {
setSequenceType(value as string);
if (value === 'genomicSequence') {
setSequenceURL(genomicURL);
} else if (value === 'proteinSequence') {
const product = props.transcript.product_generating_contexts[0].product;
setSequenceURL(
urlFor.refget({ checksum: product?.sequence.checksum as string })
);
} else {
const productType = value as 'cdna' | 'cds';
const product =
props.transcript.product_generating_contexts[0][productType];
setSequenceURL(
urlFor.refget({ checksum: product?.sequence.checksum as string })
);
}
};
if (!isProteinCodingTranscript(props.transcript)) {
radioOptions = radioOptions.filter(
(item) => item.value === 'genomicSequence' || item.value === 'cdna'
);
}
return (
<div className={styles.layout}>
<div>
<div>XXXX bp</div>
<div className={styles.sequenceWrapper}>
Fetching sequence for {sequenceType} : {sequenceURL}
</div>
</div>
<div>
<div>blast control</div>
<div className={styles.selectionWrapper}>
<RadioGroup
options={radioOptions}
onChange={handleRadioChange}
selectedOption={sequenceType}
/>
<div className={styles.reverseWrapper}>
Reverse complement checkbox
</div>
</div>
</div>
</div>
);
};
export default TranscriptSequenceView;
......@@ -20,7 +20,7 @@
margin-left: 10px;
}
& > div + div{
& > div + div {
margin-top: 3px;
}
}
......@@ -86,6 +86,10 @@
position: relative;
}
.sequenceWrapper {
margin-top: 20px;
}
.closeButton {
position: absolute;
right: 0;
......
......@@ -18,6 +18,8 @@ import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import classNames from 'classnames';
import { isEnvironment, Environment } from 'src/shared/helpers/environment';
import * as urlFor from 'src/shared/helpers/urlHelper';
import { getFormattedLocation } from 'src/shared/helpers/formatters/regionFormatter';