Unverified Commit 1238f616 authored by Jyothish's avatar Jyothish Committed by GitHub
Browse files

Use biotype from Thoas replacing all so_terms(#529)

parent 8271685b
Pipeline #178222 passed with stages
in 4 minutes and 34 seconds
......@@ -17,7 +17,7 @@
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { gql, useQuery } from '@apollo/client';
import { Pick3 } from 'ts-multipick';
import { Pick2, Pick3 } from 'ts-multipick';
import classNames from 'classnames';
import * as urlFor from 'src/shared/helpers/urlHelper';
......@@ -47,7 +47,6 @@ const GENE_QUERY = gql`
stable_id
unversioned_stable_id
symbol
so_term
transcripts {
stable_id
}
......@@ -59,6 +58,12 @@ const GENE_QUERY = gql`
length
}
}
metadata {
biotype {
label
value
}
}
}
}
`;
......@@ -70,8 +75,8 @@ type Gene = Pick<
| 'symbol'
| 'name'
| 'alternative_symbols'
| 'so_term'
> &
Pick2<FullGene, 'metadata', 'biotype'> &
Pick3<FullGene, 'slice', 'strand', 'code'> &
Pick3<FullGene, 'slice', 'location', 'length'> & {
transcripts: { stable_id: string }[];
......@@ -122,7 +127,7 @@ const GeneSummary = () => {
<span className={styles.featureSymbol}>{gene.symbol}</span>
)}
<span className={styles.stableId}>{stableId}</span>
<div>{gene.so_term.toLowerCase()}</div>
<div>{gene.metadata.biotype.label}</div>
<div>{getStrandDisplayName(gene.slice.strand.code)}</div>
<div>{getFormattedLocation(ensObjectGene.location)}</div>
</div>
......@@ -161,7 +166,10 @@ const GeneSummary = () => {
<div className={styles.downloadWrapper}>
<InstantDownloadGene
genomeId={ensObjectGene.genome_id}
gene={{ id: gene.stable_id, so_term: gene.so_term }}
gene={{
id: gene.stable_id,
biotype: gene.metadata.biotype.value
}}
/>
<CloseButton
className={styles.closeButton}
......
......@@ -13,6 +13,12 @@
font-size: 12px;
}
.questionButton {
padding-left: 10px;
position: relative;
top: 2px;
}
.value {
grid-column: value;
......@@ -48,8 +54,11 @@
flex-wrap: nowrap;
overflow: hidden;
& > span:not(:first-child) {
margin-left: 30px;
& > span:not(:nth-child(1)) {
margin-left: 40px;
}
& > span:nth-child(2) {
margin-left: 10px;
}
}
......
......@@ -39,27 +39,30 @@ import { InstantDownloadTranscript } from 'src/shared/components/instant-downloa
import ViewInApp from 'src/shared/components/view-in-app/ViewInApp';
import ExternalReference from 'src/shared/components/external-reference/ExternalReference';
import CloseButton from 'src/shared/components/close-button/CloseButton';
import QuestionButton from 'ensemblRoot/src/shared/components/question-button/QuestionButton';
import { EnsObjectGene } from 'src/shared/state/ens-object/ensObjectTypes';
import { FullTranscript } from 'src/shared/types/thoas/transcript';
import { FullGene } from 'src/shared/types/thoas/gene';
import styles from './TranscriptSummary.scss';
import { TranscriptQualityLabel } from 'ensemblRoot/src/content/app/entity-viewer/shared/components/default-transcript-label/TranscriptQualityLabel';
// TODO: narrow down the types for spliced exons and product-generating_contexts
type Transcript = Pick<
FullTranscript,
| 'stable_id'
| 'unversioned_stable_id'
| 'so_term'
| 'external_references'
| 'slice'
| 'spliced_exons'
| 'product_generating_contexts'
| 'metadata'
>;
// TODO: narrow down the type and use it in the Transcript type
type ProductGeneratingContext = Transcript['product_generating_contexts'][number];
type ProductGeneratingContext =
Transcript['product_generating_contexts'][number];
type Gene = Pick<
FullGene,
......@@ -77,7 +80,6 @@ const GENE_AND_TRANSCRIPT_QUERY = gql`
transcript(byId: { genome_id: $genomeId, stable_id: $transcriptId }) {
stable_id
unversioned_stable_id
so_term
external_references {
accession_id
url
......@@ -131,6 +133,23 @@ const GENE_AND_TRANSCRIPT_QUERY = gql`
length
}
}
metadata {
biotype {
label
value
definition
}
canonical {
value
label
definition
}
mane {
value
label
definition
}
}
}
}
`;
......@@ -163,9 +182,12 @@ const TranscriptSummary = () => {
}
const { gene, transcript } = data;
const defaultProductGeneratingContext = transcript.product_generating_contexts.find(
(entry) => entry.default
) as ProductGeneratingContext;
const metadata = transcript.metadata;
const defaultProductGeneratingContext =
transcript.product_generating_contexts.find(
(entry) => entry.default
) as ProductGeneratingContext;
const product = defaultProductGeneratingContext.product;
const stableId = getDisplayStableId(transcript);
......@@ -203,8 +225,16 @@ const TranscriptSummary = () => {
<div className={styles.value}>
<div className={styles.featureDetails}>
<span className={styles.featureSymbol}>{stableId}</span>
{transcript.so_term && (
<span>{transcript.so_term.toLowerCase()}</span>
<span className={styles.label}>
<TranscriptQualityLabel metadata={metadata} />
</span>
{metadata.biotype && (
<>
<span>{metadata.biotype.label}</span>
<div className={styles.questionButton}>
<QuestionButton helpText={metadata.biotype.definition} />
</div>
</>
)}
{transcript.slice.strand.code && (
<span>{getStrandDisplayName(transcript.slice.strand.code)}</span>
......@@ -240,7 +270,7 @@ const TranscriptSummary = () => {
</div>
</div>
{transcript.so_term === 'protein_coding' && (
{metadata.biotype.value === 'protein_coding' && (
<div className={styles.row}>
<div className={styles.label}>Protein</div>
<div className={styles.value}>
......@@ -289,7 +319,7 @@ const TranscriptSummary = () => {
genomeId={ensObjectGene.genome_id}
transcript={{
id: transcript.unversioned_stable_id,
so_term: transcript.so_term
biotype: metadata.biotype.value
}}
gene={{ id: gene.unversioned_stable_id }}
theme="light"
......@@ -307,7 +337,9 @@ const TranscriptSummary = () => {
<div className={styles.label}>Gene</div>
<div className={styles.value}>
<div>
{gene.symbol && <span className={styles.geneSymbol}>{gene.symbol}</span>}
{gene.symbol && (
<span className={styles.geneSymbol}>{gene.symbol}</span>
)}
{gene.symbol !== stableId && <span>{gene.stable_id}</span>}
</div>
</div>
......
......@@ -37,9 +37,8 @@ const ZmenuInstantDownload = (props: Props) => {
endpoint: `/lookup/id/${transcriptId}?content-type=application/json;expand=1`,
host: 'https://rest.ensembl.org'
};
const { loadingState, data, error } = useApiService<TranscriptInResponse>(
params
);
const { loadingState, data, error } =
useApiService<TranscriptInResponse>(params);
if (
loadingState === LoadingState.NOT_REQUESTED ||
......@@ -73,12 +72,12 @@ const getStableId = (id: string) => id.split(':').pop();
const preparePayload = (transcript: TranscriptInResponse) => {
const geneId = transcript.Parent;
const transcriptId = transcript.id;
const so_term = transcript.biotype;
const biotype = transcript.biotype;
return {
transcript: {
id: transcriptId,
so_term
biotype
},
gene: {
id: geneId
......
......@@ -92,7 +92,6 @@ const QUERY = gql`
transcripts {
stable_id
unversioned_stable_id
so_term
slice {
location {
start
......@@ -164,6 +163,19 @@ const QUERY = gql`
}
}
metadata {
biotype {
label
value
definition
}
tsl {
label
value
}
appris {
label
value
}
canonical {
value
label
......@@ -173,6 +185,10 @@ const QUERY = gql`
value
label
definition
ncbi_transcript {
id
url
}
}
gencode_basic {
label
......@@ -237,7 +253,8 @@ const GeneViewWithData = (props: GeneViewWithDataProps) => {
const gbUrl = urlFor.browser({ genomeId, focus: focusId });
const shouldShowFilterIndicator =
sortingRule !== SortingRule.DEFAULT || Object.values(filters).some(Boolean);
sortingRule !== SortingRule.DEFAULT ||
Object.values(filters).some((filter) => filter.selected);
const filterLabel = (
<span
......
......@@ -19,9 +19,9 @@ import { useSelector, useDispatch } from 'react-redux';
import { Pick2, Pick3 } from 'ts-multipick';
import { getFeatureCoordinates } from 'src/content/app/entity-viewer/shared/helpers/entity-helpers';
import { transcriptSortingFunctions } from 'src/content/app/entity-viewer/shared/helpers/transcripts-sorter';
import { getTranscriptSortingFunction } from 'src/content/app/entity-viewer/shared/helpers/transcripts-sorter';
import { filterTranscriptsBySOTerm } from 'src/content/app/entity-viewer/shared/helpers/transcripts-filter';
import { filterTranscripts } from 'src/content/app/entity-viewer/shared/helpers/transcripts-filter';
import {
getExpandedTranscriptIds,
......@@ -51,7 +51,6 @@ type ProductGeneratingContext = {
cds: Pick<FullCDS, 'relative_start' | 'relative_end'>;
};
type Transcript = DefaultTranscriptListItemProps['transcript'] & {
so_term: FullTranscript['so_term'];
product_generating_contexts: ProductGeneratingContext[];
} & {
spliced_exons: Array<Pick3<SplicedExon, 'exon', 'slice', 'location'>>;
......@@ -82,18 +81,17 @@ const DefaultTranscriptslist = (props: Props) => {
const { gene } = props;
const sortingFunction = transcriptSortingFunctions[sortingRule];
const sortedTranscripts = sortingFunction(gene.transcripts) as Transcript[];
const filteredTranscripts = Object.values(filters).some(Boolean)
? (filterTranscriptsBySOTerm(sortedTranscripts, filters) as Transcript[])
: sortedTranscripts;
const filteredTranscripts = filterTranscripts(gene.transcripts, filters);
const sortingFunction = getTranscriptSortingFunction<Transcript>(sortingRule);
const sortedTranscripts = sortingFunction(filteredTranscripts);
useEffect(() => {
const hasExpandedTranscripts = !!expandedTranscriptIds.length;
// Expand the first transcript by default
if (!hasExpandedTranscripts) {
dispatch(toggleTranscriptInfo(filteredTranscripts[0].stable_id));
dispatch(toggleTranscriptInfo(sortedTranscripts[0].stable_id));
}
}, []);
......@@ -106,7 +104,7 @@ const DefaultTranscriptslist = (props: Props) => {
</div>
<div className={styles.content}>
<StripedBackground {...props} />
{filteredTranscripts.map((transcript, index) => {
{sortedTranscripts.map((transcript, index) => {
const expandTranscript = expandedTranscriptIds.includes(
transcript.stable_id
);
......
......@@ -42,7 +42,6 @@ type Transcript = Pick<
export type DefaultTranscriptListItemProps = {
gene: TranscriptsListItemInfoProps['gene'];
isDefault?: boolean;
transcript: Transcript;
rulerTicks: TicksAndScale;
expandTranscript: boolean;
......
......@@ -15,6 +15,7 @@
*/
import React from 'react';
import faker from 'faker';
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MemoryRouter } from 'react-router';
......@@ -25,7 +26,11 @@ import {
} from './TranscriptsListItemInfo';
import { createGene } from 'tests/fixtures/entity-viewer/gene';
import { createTranscript } from 'tests/fixtures/entity-viewer/transcript';
import {
createTranscript,
createTranscriptMetadata
} from 'tests/fixtures/entity-viewer/transcript';
import { createExternalReference } from 'ensemblRoot/tests/fixtures/entity-viewer/external-reference';
jest.mock('src/shared/components/view-in-app/ViewInApp', () => () => (
<div data-test-id="viewInApp">ViewInApp</div>
......@@ -44,6 +49,50 @@ const gene = createGene({ transcripts: [transcript] });
const expandDownload = false;
const expandMoreInfo = false;
const createGencodeBasicTranscript = () => {
const metadata = createTranscriptMetadata({
gencode_basic: {
label: 'gencode basic',
value: faker.lorem.word(),
definition: faker.lorem.sentence()
}
});
const transcript = createTranscript({ metadata });
return transcript;
};
const createMANETranscript = () => {
const metadata = createTranscriptMetadata({
mane: {
label: 'MANE Select',
value: 'select',
definition: faker.lorem.sentence(),
ncbi_transcript: {
id: faker.lorem.word(),
url: faker.lorem.sentence()
}
}
});
const transcript = createTranscript({ metadata });
return transcript;
};
const createCCDSXrefTranscript = () => {
const xref = {
source: {
name: 'CCDS',
id: faker.datatype.uuid(),
url: faker.internet.url()
}
};
const transcript = createTranscript({
external_references: [createExternalReference(xref)]
});
return transcript;
};
const defaultProps = {
gene,
transcript,
......@@ -107,4 +156,40 @@ describe('<TranscriptsListItemInfo /', () => {
userEvent.click(proteinLink);
expect(defaultProps.onProteinLinkClick).toHaveBeenCalled();
});
it('displays metadata when it is available', () => {
const { queryByText } = renderComponent({
transcript: createGencodeBasicTranscript(),
expandMoreInfo: true
});
const metadataLabel = queryByText('gencode basic');
expect(metadataLabel).toBeTruthy();
});
it('displays CCDS when it is available in external references', () => {
const { queryByText } = renderComponent({
transcript: createCCDSXrefTranscript(),
expandMoreInfo: true
});
const CCDSLabel = queryByText('CCDS');
expect(CCDSLabel).toBeTruthy();
});
it('displays the Refseq with a link', () => {
const MANETranscript = createMANETranscript();
const refseqId = MANETranscript.metadata.mane?.ncbi_transcript?.id;
const refseqUrl = MANETranscript.metadata.mane?.ncbi_transcript?.url;
const { container, queryByText } = renderComponent({
transcript: MANETranscript,
expandMoreInfo: true
});
const refseqLabel = queryByText('RefSeq match');
expect(refseqLabel).toBeTruthy();
const refseqLink = [...container.querySelectorAll('a')].find(
(link) => link.textContent === refseqId
) as HTMLElement;
expect(refseqLink.getAttribute('href')).toBe(refseqUrl);
});
});
......@@ -56,14 +56,9 @@ import styles from './TranscriptsListItemInfo.scss';
type Gene = Pick<FullGene, 'unversioned_stable_id' | 'stable_id'>;
type Transcript = Pick<
FullTranscript,
'stable_id' | 'unversioned_stable_id' | 'so_term' | 'external_references'
'stable_id' | 'unversioned_stable_id' | 'external_references' | 'metadata'
> &
Pick2<FullTranscript, 'slice', 'location'> &
Pick2<
FullTranscript,
'metadata',
'canonical' | 'mane' | 'gencode_basic' | 'appris' | 'tsl'
> &
Pick3<FullTranscript, 'slice', 'region', 'name'> & {
spliced_exons: Array<
Pick2<SplicedExon, 'exon', 'stable_id'> &
......@@ -126,6 +121,7 @@ export const TranscriptsListItemInfo = (
const transcriptCCDS = transcript.external_references.find(
(xref) => xref.source.name === 'CCDS'
);
const transcriptNCBI = transcript.metadata.mane?.ncbi_transcript;
const hasRelevantMetadata = (
['gencode_basic', 'tsl', 'appris'] as const
......@@ -172,14 +168,24 @@ export const TranscriptsListItemInfo = (
)}
</div>
)}
{!!transcriptCCDS && (
{(!!transcriptCCDS || !!transcriptNCBI) && (
<div className={styles.moreInfoColumn}>
<ExternalReference
classNames={{ label: styles.normalText }}
label={'CCDS'}
to={transcriptCCDS.url}
linkText={transcriptCCDS.accession_id}
/>
{!!transcriptNCBI && (
<ExternalReference
classNames={{ label: styles.normalText }}
label={'RefSeq match'}
to={transcriptNCBI.url}
linkText={transcriptNCBI.id}
/>
)}
{!!transcriptCCDS && (
<ExternalReference
classNames={{ label: styles.normalText }}
label={'CCDS'}
to={transcriptCCDS.url}
linkText={transcriptCCDS.accession_id}
/>
)}
</div>
)}
</>
......@@ -192,7 +198,7 @@ export const TranscriptsListItemInfo = (
<div className={midStyles}>
<div className={styles.topLeft}>
<div>
<strong>{transcript.so_term}</strong>
<strong>{transcript.metadata.biotype.label}</strong>
</div>
<div>{getTranscriptLocation()}</div>
</div>
......@@ -261,7 +267,7 @@ const renderInstantDownload = ({
genomeId={genomeId}
transcript={{
id: transcript.stable_id,
so_term: transcript.so_term
biotype: transcript.metadata.biotype.value
}}
gene={{ id: gene.stable_id }}
/>
......
......@@ -60,7 +60,6 @@ const QUERY = gql`
}
transcripts {
stable_id
so_term
slice {
location {
length
......@@ -106,7 +105,6 @@ const QUERY = gql`
type Transcript = {
stable_id: string;
so_term: string;