Unverified Commit 3ac7d6a3 authored by Ridwan Amode's avatar Ridwan Amode Committed by GitHub
Browse files

ENSWBSITES-1110: Display transcript metadata in transcript accordian (#526)

* ENSWBSITES-1110: Display transcript metadata in transcript accordian
- retrieve metadata from thoas (gencode_basic, tsl, appris, ccds)
- update component to display metadata
- Modify transcript object by setting each key instead of overwriting
- rename git-column-gap to column-gap since new CSS standard
parent 07aed325
Pipeline #176725 passed with stages
in 4 minutes and 39 seconds
......@@ -154,6 +154,15 @@ const QUERY = gql`
}
}
}
external_references {
accession_id
name
url
source {
id
name
}
}
metadata {
canonical {
value
......@@ -165,6 +174,15 @@ const QUERY = gql`
label
definition
}
gencode_basic {
label
}
appris {
label
}
tsl {
label
}
}
}
}
......
......@@ -26,6 +26,7 @@ import { filterTranscriptsBySOTerm } from 'src/content/app/entity-viewer/shared/
import {
getExpandedTranscriptIds,
getExpandedTranscriptDownloadIds,
getExpandedTranscriptMoreInfoIds,
getFilters,
getSortingRule
} from 'src/content/app/entity-viewer/state/gene-view/transcripts/geneViewTranscriptsSelectors';
......@@ -72,6 +73,9 @@ const DefaultTranscriptslist = (props: Props) => {
const expandedTranscriptDownloadIds = useSelector(
getExpandedTranscriptDownloadIds
);
const expandedTranscriptMoreInfoIds = useSelector(
getExpandedTranscriptMoreInfoIds
);
const sortingRule = useSelector(getSortingRule);
const filters = useSelector(getFilters);
const dispatch = useDispatch();
......@@ -109,6 +113,9 @@ const DefaultTranscriptslist = (props: Props) => {
const expandDownload = expandedTranscriptDownloadIds.includes(
transcript.stable_id
);
const expandMoreInfo = expandedTranscriptMoreInfoIds.includes(
transcript.stable_id
);
return (
<DefaultTranscriptsListItem
......@@ -118,6 +125,7 @@ const DefaultTranscriptslist = (props: Props) => {
rulerTicks={props.rulerTicks}
expandTranscript={expandTranscript}
expandDownload={expandDownload}
expandMoreInfo={expandMoreInfo}
/>
);
})}
......
......@@ -51,6 +51,7 @@ describe('<DefaultTranscriptListItem />', () => {
rulerTicks: createRulerTicks(),
expandTranscript: false,
expandDownload: false,
expandMoreInfo: false,
toggleTranscriptInfo: toggleTranscriptInfo
};
......
......@@ -47,6 +47,7 @@ export type DefaultTranscriptListItemProps = {
rulerTicks: TicksAndScale;
expandTranscript: boolean;
expandDownload: boolean;
expandMoreInfo: boolean;
toggleTranscriptInfo: (id: string) => void;
};
......@@ -101,6 +102,7 @@ export const DefaultTranscriptListItem = (
gene={props.gene}
transcript={props.transcript}
expandDownload={props.expandDownload}
expandMoreInfo={props.expandMoreInfo}
/>
) : null}
</div>
......
......@@ -9,10 +9,11 @@
display: grid;
grid-template-areas:
'left middle right'
'moreinformation . .'
'downloadLink . .'
'moreinformationLink . .'
'moreInformation moreInformation moreInformation'
'downloadLink . .'
'download download download';
grid-column-gap: 10px;
column-gap: 10px;
background: $soft-black;
padding: 15px 30px;
......@@ -30,6 +31,7 @@
.topLeft {
grid-area: left;
width: 190px;
margin-bottom: 12px;
div {
padding-bottom: 6px;
}
......@@ -51,8 +53,31 @@
}
}
.moreInformationLink {
padding-top: 6px;
color: $blue;
cursor: pointer;
}
.moreInformation {
padding-top: 16px;
grid-area: moreInformation;
display: grid;
grid-template-columns: 190px auto;
column-gap: 20px;
padding-top: 6px;
padding-left: 12px;
margin-bottom: 6px;
}
.moreInfoColumn {
width: 190px;
div {
padding-bottom: 6px;
}
}
.normalText {
font-weight: $normal;
}
.downloadLink {
......
......@@ -42,12 +42,15 @@ jest.mock('src/shared/components/instant-download', () => ({
const transcript = createTranscript();
const gene = createGene({ transcripts: [transcript] });
const expandDownload = false;
const expandMoreInfo = false;
const defaultProps = {
gene,
transcript,
expandDownload,
expandMoreInfo,
toggleTranscriptDownload: jest.fn(),
toggleTranscriptMoreInfo: jest.fn(),
onProteinLinkClick: jest.fn()
};
......
......@@ -36,8 +36,12 @@ import { buildFocusIdForUrl } from 'src/shared/state/ens-object/ensObjectHelpers
import { InstantDownloadTranscript } from 'src/shared/components/instant-download';
import ViewInApp from 'src/shared/components/view-in-app/ViewInApp';
import ShowHide from 'src/shared/components/show-hide/ShowHide';
import ExternalReference from 'src/shared/components/external-reference/ExternalReference';
import { toggleTranscriptDownload } from 'src/content/app/entity-viewer/state/gene-view/transcripts/geneViewTranscriptsSlice';
import {
toggleTranscriptDownload,
toggleTranscriptMoreInfo
} from 'src/content/app/entity-viewer/state/gene-view/transcripts/geneViewTranscriptsSlice';
import { clearExpandedProteins } from 'src/content/app/entity-viewer/state/gene-view/proteins/geneViewProteinsSlice';
import { FullGene } from 'src/shared/types/thoas/gene';
......@@ -52,9 +56,14 @@ 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'
'stable_id' | 'unversioned_stable_id' | 'so_term' | 'external_references'
> &
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'> &
......@@ -80,7 +89,9 @@ export type TranscriptsListItemInfoProps = {
gene: Gene;
transcript: Transcript;
expandDownload: boolean;
expandMoreInfo: boolean;
toggleTranscriptDownload: (id: string) => void;
toggleTranscriptMoreInfo: (id: string) => void;
onProteinLinkClick: () => void;
};
......@@ -112,6 +123,13 @@ export const TranscriptsListItemInfo = (
const mainStyles = classNames(transcriptsListStyles.row, styles.listItemInfo);
const midStyles = classNames(transcriptsListStyles.middle, styles.middle);
const transcriptCCDS = transcript.external_references.find(
(xref) => xref.source.name === 'CCDS'
);
const hasRelevantMetadata = (
['gencode_basic', 'tsl', 'appris'] as const
).some((key) => transcript.metadata[key]);
const focusIdForUrl = buildFocusIdForUrl({
type: 'gene',
......@@ -138,6 +156,36 @@ export const TranscriptsListItemInfo = (
return urlFor.browser({ genomeId: genomeId, focus: focusIdForUrl });
};
const moreInfoContent = () => {
return (
<>
{hasRelevantMetadata && (
<div className={styles.moreInfoColumn}>
{transcript.metadata.gencode_basic?.label && (
<div>{transcript.metadata.gencode_basic?.label}</div>
)}
{transcript.metadata?.tsl && (
<div>{transcript.metadata.tsl?.label}</div>
)}
{transcript.metadata?.appris && (
<div>{transcript.metadata.appris?.label}</div>
)}
</div>
)}
{!!transcriptCCDS && (
<div className={styles.moreInfoColumn}>
<ExternalReference
classNames={{ label: styles.normalText }}
label={'CCDS'}
to={transcriptCCDS.url}
linkText={transcriptCCDS.accession_id}
/>
</div>
)}
</>
);
};
return (
<div className={mainStyles}>
<div className={transcriptsListStyles.left}></div>
......@@ -170,7 +218,18 @@ export const TranscriptsListItemInfo = (
</div>
</div>
<div className={styles.moreInformation}>More information</div>
{(hasRelevantMetadata || !!transcriptCCDS) && (
<ShowHide
onClick={() => props.toggleTranscriptMoreInfo(transcript.stable_id)}
label="More information"
isExpanded={props.expandMoreInfo}
classNames={{ wrapper: styles.moreInformationLink }}
/>
)}
{props.expandMoreInfo && (
<div className={styles.moreInformation}>{moreInfoContent()}</div>
)}
<ShowHide
onClick={() => props.toggleTranscriptDownload(transcript.stable_id)}
......@@ -212,6 +271,7 @@ const renderInstantDownload = ({
const mapDispatchToProps = {
toggleTranscriptDownload,
toggleTranscriptMoreInfo,
onProteinLinkClick: clearExpandedProteins
};
......
......@@ -109,30 +109,25 @@ const createTranscriptWithSmallestExons = () => {
const createMANETranscript = () => {
const transcript = createTranscript();
transcript.metadata = {
canonical: {
label: 'Ensembl canonical',
value: true,
definition: faker.lorem.sentence()
},
mane: {
label: 'MANE Select',
value: 'select',
definition: faker.lorem.sentence()
}
transcript.metadata.canonical = {
label: 'Ensembl canonical',
value: true,
definition: faker.lorem.sentence()
};
transcript.metadata.mane = {
label: 'MANE Select',
value: 'select',
definition: faker.lorem.sentence()
};
return transcript;
};
const createOtherMANETranscript = () => {
const transcript = createTranscript();
transcript.metadata = {
canonical: null,
mane: {
label: 'MANE Plus Clinical',
value: 'plus_clinical',
definition: faker.lorem.sentence()
}
transcript.metadata.mane = {
label: 'MANE Plus Clinical',
value: 'plus_clinical',
definition: faker.lorem.sentence()
};
return transcript;
};
......
......@@ -51,6 +51,13 @@ export const getExpandedTranscriptDownloadIds = (
return transcriptsSlice?.expandedDownloadIds ?? [];
};
export const getExpandedTranscriptMoreInfoIds = (
state: RootState
): string[] => {
const transcriptsSlice = getSliceForGene(state);
return transcriptsSlice?.expandedMoreInfoIds ?? [];
};
export const getFilters = (state: RootState): Filters => {
const transcriptsSlice = getSliceForGene(state);
return transcriptsSlice?.filters ?? {};
......
......@@ -26,7 +26,8 @@ import {
import {
getExpandedTranscriptIds,
getExpandedTranscriptDownloadIds
getExpandedTranscriptDownloadIds,
getExpandedTranscriptMoreInfoIds
} from './geneViewTranscriptsSelectors';
import { RootState } from 'src/store';
......@@ -42,6 +43,7 @@ export enum SortingRule {
export type TranscriptsStatePerGene = {
expandedIds: string[];
expandedDownloadIds: string[];
expandedMoreInfoIds: string[];
filters: Filters;
sortingRule: SortingRule;
};
......@@ -57,110 +59,129 @@ export type Filters = { [filter: string]: boolean };
const defaultStatePerGene: TranscriptsStatePerGene = {
expandedIds: [],
expandedDownloadIds: [],
expandedMoreInfoIds: [],
filters: {},
sortingRule: SortingRule.DEFAULT
};
export const setFilters = (
filters: Filters
): ThunkAction<void, any, null, Action<string>> => (
dispatch,
getState: () => RootState
) => {
const state = getState();
const activeGenomeId = getEntityViewerActiveGenomeId(state);
const activeEntityId = getEntityViewerActiveEntityId(state);
if (!activeGenomeId || !activeEntityId) {
return;
}
dispatch(
transcriptsSlice.actions.updateFilters({
activeGenomeId,
activeEntityId,
filters
})
);
};
export const setFilters =
(filters: Filters): ThunkAction<void, any, null, Action<string>> =>
(dispatch, getState: () => RootState) => {
const state = getState();
const activeGenomeId = getEntityViewerActiveGenomeId(state);
const activeEntityId = getEntityViewerActiveEntityId(state);
if (!activeGenomeId || !activeEntityId) {
return;
}
dispatch(
transcriptsSlice.actions.updateFilters({
activeGenomeId,
activeEntityId,
filters
})
);
};
export const setSortingRule = (
sortingRule: SortingRule
): ThunkAction<void, any, null, Action<string>> => (
dispatch,
getState: () => RootState
) => {
const state = getState();
const activeGenomeId = getEntityViewerActiveGenomeId(state);
const activeEntityId = getEntityViewerActiveEntityId(state);
if (!activeGenomeId || !activeEntityId) {
return;
}
export const setSortingRule =
(sortingRule: SortingRule): ThunkAction<void, any, null, Action<string>> =>
(dispatch, getState: () => RootState) => {
const state = getState();
const activeGenomeId = getEntityViewerActiveGenomeId(state);
const activeEntityId = getEntityViewerActiveEntityId(state);
if (!activeGenomeId || !activeEntityId) {
return;
}
dispatch(
transcriptsSlice.actions.updateSortingRule({
activeGenomeId,
activeEntityId,
sortingRule
})
);
};
dispatch(
transcriptsSlice.actions.updateSortingRule({
activeGenomeId,
activeEntityId,
sortingRule
})
);
};
export const toggleTranscriptInfo = (
transcriptId: string
): ThunkAction<void, any, null, Action<string>> => (
dispatch,
getState: () => RootState
) => {
const state = getState();
const activeGenomeId = getEntityViewerActiveGenomeId(state);
const activeEntityId = getEntityViewerActiveEntityId(state);
if (!activeGenomeId || !activeEntityId) {
return;
}
export const toggleTranscriptInfo =
(transcriptId: string): ThunkAction<void, any, null, Action<string>> =>
(dispatch, getState: () => RootState) => {
const state = getState();
const activeGenomeId = getEntityViewerActiveGenomeId(state);
const activeEntityId = getEntityViewerActiveEntityId(state);
if (!activeGenomeId || !activeEntityId) {
return;
}
const expandedIds = new Set<string>(getExpandedTranscriptIds(state));
if (expandedIds.has(transcriptId)) {
expandedIds.delete(transcriptId);
} else {
expandedIds.add(transcriptId);
}
const expandedIds = new Set<string>(getExpandedTranscriptIds(state));
if (expandedIds.has(transcriptId)) {
expandedIds.delete(transcriptId);
} else {
expandedIds.add(transcriptId);
}
dispatch(
transcriptsSlice.actions.updateExpandedTranscripts({
activeGenomeId,
activeEntityId,
expandedIds: [...expandedIds.values()]
})
);
};
dispatch(
transcriptsSlice.actions.updateExpandedTranscripts({
activeGenomeId,
activeEntityId,
expandedIds: [...expandedIds.values()]
})
);
};
export const toggleTranscriptDownload = (
transcriptId: string
): ThunkAction<void, any, null, Action<string>> => (
dispatch,
getState: () => RootState
) => {
const state = getState();
const activeGenomeId = getEntityViewerActiveGenomeId(state);
const activeEntityId = getEntityViewerActiveEntityId(state);
if (!activeGenomeId || !activeEntityId) {
return;
}
export const toggleTranscriptDownload =
(transcriptId: string): ThunkAction<void, any, null, Action<string>> =>
(dispatch, getState: () => RootState) => {
const state = getState();
const activeGenomeId = getEntityViewerActiveGenomeId(state);
const activeEntityId = getEntityViewerActiveEntityId(state);
if (!activeGenomeId || !activeEntityId) {
return;
}
const expandedIds = new Set<string>(getExpandedTranscriptDownloadIds(state));
if (expandedIds.has(transcriptId)) {
expandedIds.delete(transcriptId);
} else {
expandedIds.add(transcriptId);
}
const expandedIds = new Set<string>(
getExpandedTranscriptDownloadIds(state)
);
if (expandedIds.has(transcriptId)) {
expandedIds.delete(transcriptId);
} else {
expandedIds.add(transcriptId);
}
dispatch(
transcriptsSlice.actions.updateExpandedDownloads({
activeGenomeId,
activeEntityId,
expandedIds: [...expandedIds.values()]
})
);
};
dispatch(
transcriptsSlice.actions.updateExpandedDownloads({
activeGenomeId,
activeEntityId,
expandedIds: [...expandedIds.values()]
})
);