Unverified Commit ff75bed5 authored by Manoj Pandian Sakthivel's avatar Manoj Pandian Sakthivel Committed by GitHub
Browse files

Update Gene & Transcript drawer view to retrieve data from Thoas (#445)

parent 013213ed
......@@ -48,27 +48,28 @@ jest.mock('./drawer/Drawer', () => () => <div>Drawer</div>);
jest.mock('ensembl-genome-browser', () => {
return;
});
jest.mock('src/gql-client', () => ({ client: jest.fn() }));
const defaultProps: BrowserProps = {
activeGenomeId: faker.lorem.words(),
activeEnsObjectId: faker.lorem.words(),
browserActivated: false,
browserNavOpened: false,
browserQueryParams: {},
chrLocation: createChrLocationValues().tupleValue,
isDrawerOpened: false,
isTrackPanelOpened: false,
exampleEnsObjects: [],
toggleTrackPanel: jest.fn(),
toggleDrawer: jest.fn(),
viewportWidth: BreakpointWidth.DESKTOP
};
describe('<Browser />', () => {
afterEach(() => {
jest.resetAllMocks();
});
const defaultProps: BrowserProps = {
activeGenomeId: faker.lorem.words(),
activeEnsObjectId: faker.lorem.words(),
browserActivated: false,
browserNavOpened: false,
browserQueryParams: {},
chrLocation: createChrLocationValues().tupleValue,
isDrawerOpened: false,
isTrackPanelOpened: false,
exampleEnsObjects: [],
toggleTrackPanel: jest.fn(),
toggleDrawer: jest.fn(),
viewportWidth: BreakpointWidth.DESKTOP
};
const wrappingComponent = (props: any) => (
<MemoryRouter>{props.children}</MemoryRouter>
);
......
......@@ -15,11 +15,13 @@
*/
import React, { useEffect } from 'react';
import { ApolloProvider } from '@apollo/client';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import useBrowserRouting from './hooks/useBrowserRouting';
import { client } from 'src/gql-client';
import analyticsTracking from 'src/services/analytics-service';
import * as urlFor from 'src/shared/helpers/urlHelper';
import { BreakpointWidth } from 'src/global/globalConfig';
......@@ -117,26 +119,28 @@ export const Browser = (props: BrowserProps) => {
);
return (
<div className={styles.browserInnerWrapper}>
<BrowserAppBar onSpeciesSelect={changeGenomeId} />
{props.browserQueryParams.focus ? (
<StandardAppLayout
mainContent={mainContent}
sidebarContent={<TrackPanel />}
sidebarNavigation={<TrackPanelTabs />}
sidebarToolstripContent={<TrackPanelBar />}
onSidebarToggle={onSidebarToggle}
topbarContent={<BrowserBar />}
isSidebarOpen={props.isTrackPanelOpened}
isDrawerOpen={props.isDrawerOpened}
drawerContent={<Drawer />}
onDrawerClose={toggleDrawer}
viewportWidth={props.viewportWidth}
/>
) : (
<ExampleObjectLinks {...props} />
)}
</div>
<ApolloProvider client={client}>
<div className={styles.browserInnerWrapper}>
<BrowserAppBar onSpeciesSelect={changeGenomeId} />
{props.browserQueryParams.focus ? (
<StandardAppLayout
mainContent={mainContent}
sidebarContent={<TrackPanel />}
sidebarNavigation={<TrackPanelTabs />}
sidebarToolstripContent={<TrackPanelBar />}
onSidebarToggle={onSidebarToggle}
topbarContent={<BrowserBar />}
isSidebarOpen={props.isTrackPanelOpened}
isDrawerOpen={props.isDrawerOpened}
drawerContent={<Drawer />}
onDrawerClose={toggleDrawer}
viewportWidth={props.viewportWidth}
/>
) : (
<ExampleObjectLinks {...props} />
)}
</div>
</ApolloProvider>
);
};
......
......@@ -5,50 +5,3 @@
overflow: auto;
font-size: 13px;
}
.drawerView {
overflow-y: auto;
margin-top: 10px;
h2 {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
p {
margin: 0px;
}
.nextLine {
display: block;
margin-bottom: 10px;
}
.container {
display: grid;
grid-template-columns: 100px 1fr;
grid-column-gap: 1em;
grid-row-gap: 1em;
}
.label {
font-weight: $light;
text-align: right;
}
.details {
display: flex;
text-align: left;
}
.mainDetail {
font-weight: $bold;
}
.secondaryDetail {
font-weight: $light;
margin-left: 10px;
}
}
@import 'src/styles/common';
.row {
display: grid;
grid-template-columns: [label] 120px [value] auto;
grid-column-gap: 10px;
margin-bottom:3px;
.label {
grid-column: label;
font-weight: $light;
text-align: right;
}
.value {
grid-column: value;
}
}
.spaceAbove{
margin-top: 30px;
}
.featureDetails {
display: flex;
align-items: center;
white-space: nowrap;
flex-wrap: nowrap;
overflow: hidden;
& > div:not(:first-child) {
margin-left: 30px;
}
}
.featureSymbol {
font-weight: $bold;
}
.stableId{
font-weight: $bold;
}
.featureSymbol + .stableId{
margin-left: 12px;
font-weight: $light;
}
......@@ -16,30 +16,134 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { gql, useQuery } from '@apollo/client';
import { getDisplayStableId } from 'src/shared/state/ens-object/ensObjectHelpers';
import * as urlFor from 'src/shared/helpers/urlHelper';
import { getFormattedLocation } from 'src/shared/helpers/formatters/regionFormatter';
import { getStrandDisplayName } from 'src/shared/helpers/formatters/strandFormatter';
import {
buildFocusIdForUrl,
getDisplayStableId
} from 'src/shared/state/ens-object/ensObjectHelpers';
import { getBrowserActiveEnsObject } from 'src/content/app/browser/browserSelectors';
import ViewInApp from 'src/shared/components/view-in-app/ViewInApp';
import { EnsObjectGene } from 'src/shared/state/ens-object/ensObjectTypes';
import { Gene as GeneFromGraphql } from 'src/shared/types/thoas/gene';
import styles from './GeneSummary.scss';
const GENE_QUERY = gql`
query Gene($genomeId: String!, $geneId: String!) {
gene(byId: { genome_id: $genomeId, stable_id: $geneId }) {
alternative_symbols
name
stable_id
symbol
so_term
transcripts {
stable_id
}
slice {
strand {
code
value
}
location {
length
}
}
}
}
`;
import styles from 'src/content/app/browser/drawer/Drawer.scss';
type Gene = Required<
Pick<
GeneFromGraphql,
| 'stable_id'
| 'symbol'
| 'name'
| 'alternative_symbols'
| 'so_term'
| 'transcripts'
| 'slice'
>
>;
const GeneSummary = () => {
const ensObject = useSelector(getBrowserActiveEnsObject) as EnsObjectGene;
const ensObjectGene = useSelector(getBrowserActiveEnsObject) as EnsObjectGene;
const { data, loading } = useQuery<{ gene: Gene }>(GENE_QUERY, {
variables: {
geneId: ensObjectGene.stable_id,
genomeId: ensObjectGene.genome_id
},
skip: !ensObjectGene.stable_id
});
if (loading || !data?.gene) {
return null;
}
if (!data?.gene) {
return <div>No data available</div>;
}
const { gene } = data;
const stableId = getDisplayStableId(gene);
const focusId = buildFocusIdForUrl({
type: 'gene',
objectId: gene.stable_id as string
});
const entityViewerUrl = urlFor.entityViewer({
genomeId: ensObjectGene.genome_id,
entityId: focusId
});
return (
<div className={styles.drawerView}>
<div className={styles.container}>
<div>
<div className={styles.row}>
<div className={styles.label}>Gene</div>
<div className={styles.details}>
<span className={styles.mainDetail}>{ensObject.label}</span>
<div className={styles.value}>
<div className={styles.featureDetails}>
{gene.symbol && (
<span className={styles.featureSymbol}>{gene.symbol}</span>
)}
<span className={styles.stableId}>{stableId}</span>
<div>{gene.so_term.toLowerCase()}</div>
<div>{getStrandDisplayName(gene.slice.strand.code)}</div>
<div>{getFormattedLocation(ensObjectGene.location)}</div>
</div>
</div>
</div>
<div className={`${styles.row} ${styles.spaceAbove}`}>
<div className={styles.label}>Gene name</div>
<div className={styles.value}>{gene.name}</div>
</div>
{gene.alternative_symbols.length > 0 && (
<div className={`${styles.row} ${styles.spaceAbove}`}>
<div className={styles.label}>Synonyms</div>
<div className={styles.value}>
{gene.alternative_symbols.join(', ')}
</div>
</div>
)}
<div className={styles.label}>Stable ID</div>
<div className={styles.details}>{getDisplayStableId(ensObject)}</div>
<div className={`${styles.row} ${styles.spaceAbove}`}>
<div className={styles.value}>
{`${gene.transcripts.length} transcripts`}
</div>
</div>
<div className={styles.label}>Description</div>
<div className={styles.details}>{ensObject.description || '--'}</div>
<div className={`${styles.row} ${styles.spaceAbove}`}>
<div className={styles.value}>
<ViewInApp links={{ entityViewer: entityViewerUrl }} />
</div>
</div>
</div>
);
......
......@@ -24,27 +24,3 @@
grid-column: value;
}
}
.featureDetails {
display: flex;
align-items: center;
white-space: nowrap;
flex-wrap: nowrap;
overflow: hidden;
& > div:not(:first-child) {
margin-left: 30px;
}
}
.featureSymbol {
font-weight: $bold;
}
.stableId {
font-weight: $bold;
}
.featureSymbol + .stableId {
margin-left: 12px;
color: $dark-grey;
font-weight: $light;
}
@import 'src/styles/common';
.row {
display: grid;
grid-template-columns: [label] 130px [value] auto;
grid-column-gap: 10px;
margin-bottom:3px;
.label {
grid-column: label;
font-weight: $light;
text-align: right;
font-size: 12px;
}
.value {
grid-column: value;
& > span + span {
margin-left: 10px;
}
& > div + div{
margin-top: 3px;
}
}
}
.spaceAbove{
margin-top: 30px;
}
.lightText{
font-weight: $light;
}
.unit{
font-weight: $light;
font-size: 12px;
margin-left: 5px;
}
.value .featureDetails {
display: flex;
align-items: center;
white-space: nowrap;
flex-wrap: nowrap;
overflow: hidden;
& > span:not(:first-child) {
margin-left: 30px;
}
}
.featureSymbol {
font-weight: $bold;
}
.stableId{
font-weight: $bold;
}
.featureSymbol + .stableId{
margin-left: 12px;
color: $dark-grey;
font-weight: $light;
}
.downloadLink{
color: $blue;
cursor: pointer;
}
.downloadWrapper{
display: inline-block;
margin-left: 40px;
min-width: 600px;
position: relative;
}
.closeButton{
position: absolute;
right: 0;
top: 0;
}
......@@ -14,70 +14,323 @@
* limitations under the License.
*/
import React from 'react';
import get from 'lodash/get';
import find from 'lodash/find';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { gql, useQuery } from '@apollo/client';
import { getDisplayStableId } from 'src/shared/state/ens-object/ensObjectHelpers';
import * as urlFor from 'src/shared/helpers/urlHelper';
import { getFormattedLocation } from 'src/shared/helpers/formatters/regionFormatter';
import { getStrandDisplayName } from 'src/shared/helpers/formatters/strandFormatter';
import {
getDisplayStableId,
buildFocusIdForUrl
} from 'src/shared/state/ens-object/ensObjectHelpers';
import { getBrowserActiveEnsObject } from 'src/content/app/browser/browserSelectors';
import { getCommaSeparatedNumber } from 'src/shared/helpers/formatters/numberFormatter';
// TODO: check if this can be moved to a common place
import {
getNumberOfCodingExons,
getSplicedRNALength
} from 'src/content/app/entity-viewer/shared/helpers/entity-helpers';
import { InstantDownloadTranscript } from 'src/shared/components/instant-download';
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 { EnsObjectGene } from 'src/shared/state/ens-object/ensObjectTypes';
import { Transcript as TranscriptFromGraphql } from 'src/shared/types/thoas/transcript';
import { Gene as GeneFromGraphql } from 'src/shared/types/thoas/gene';
import { ProductGeneratingContext } from 'src/shared/types/thoas/productGeneratingContext';
import styles from 'src/content/app/browser/drawer/Drawer.scss';
import styles from './TranscriptSummary.scss';
// TODO: Once we start supporting multiple transcripts, we need to either remove this constant or move it to trackConfig
const TRANSCRIPT_GENE_NAME = 'track:gene-feat-1';
type Transcript = Required<
Pick<
TranscriptFromGraphql,
| 'stable_id'
| 'unversioned_stable_id'
| 'symbol'
| 'so_term'
| 'external_references'
| 'slice'
| 'spliced_exons'
| 'product_generating_contexts'
>
>;
const TranscriptSummary = () => {
const ensObject = useSelector(getBrowserActiveEnsObject) as EnsObjectGene;
type Gene = Required<
Pick<
GeneFromGraphql,
'stable_id' | 'unversioned_stable_id' | 'symbol' | 'name'
>
>;
if (!ensObject.track) {
return null;
const GENE_AND_TRANSCRIPT_QUERY = gql`
query Gene($genomeId: String!, $geneId: String!, $transcriptId: String!) {
gene(byId: { genome_id: $genomeId, stable_id: $geneId }) {
name
stable_id
unversioned_stable_id
symbol
}
transcript(byId: { genome_id: $genomeId, stable_id: $transcriptId }) {
stable_id
unversioned_stable_id
so_term
symbol
external_references {
accession_id
url
source {
id
}
}
spliced_exons {
relative_location {
start
end
}
exon {
stable_id
slice {
location {
length
}
}