Skip to content

Commit

Permalink
Implement gene header design
Browse files Browse the repository at this point in the history
  • Loading branch information
calvinlu3 committed Nov 8, 2024
1 parent 8b4edf6 commit d3350ea
Show file tree
Hide file tree
Showing 35 changed files with 731 additions and 230 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
"axios": "1.6.8",
"bootstrap": "5.3.3",
"classnames": "^2.5.1",
"date-fns": "^4.1.0",
"dayjs": "1.11.11",
"diff-match-patch": "^1.0.5",
"firebase": "^10.11.1",
Expand Down
5 changes: 3 additions & 2 deletions src/main/webapp/app/components/tabs/CurationToolsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,13 @@ export function CurationToolsTab({
}, []);

function isReleasedFlag(flag: IFlag) {
return flag.type === 'GENE_PANEL' && flag.flag === 'ONCOKB';
const flagFlag = isGermline ? 'ONCOKB_GERMLINE' : 'ONCOKB_SOMATIC';
return flag.type === 'GENE_PANEL' && flag.flag === flagFlag;
}

useEffect(() => {
const geneData = geneEntities?.find(entity => entity.hugoSymbol === geneName);
setIsReleased(geneData === undefined ? false : geneIsReleased(geneData));
setIsReleased(geneData === undefined ? false : geneIsReleased(geneData, isGermline));
geneToUpdate.current = geneData;
}, [geneEntities, geneName]);

Expand Down
4 changes: 2 additions & 2 deletions src/main/webapp/app/components/tabs/GeneListPageToolsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import SaveGeneButton from 'app/shared/button/SaveGeneButton';

export interface IGeneListPageToolsTab extends StoreProps {
metaData: MetaCollection | null;
isGermline: boolean;
}

function GeneListPageToolsTab({ metaData, isDev, createGene, isGermline }: IGeneListPageToolsTab) {
Expand Down Expand Up @@ -76,9 +75,10 @@ function GeneListPageToolsTab({ metaData, isDev, createGene, isGermline }: IGene
);
}

const mapStoreToProps = ({ firebaseGeneService, authStore }: IRootStore) => ({
const mapStoreToProps = ({ firebaseGeneService, authStore, routerStore }: IRootStore) => ({
createGene: firebaseGeneService.createGene,
isDev: hasAnyAuthority(authStore.account.authorities, [AUTHORITIES.DEV]),
isGermline: routerStore.isGermline,
});

type StoreProps = Partial<ReturnType<typeof mapStoreToProps>>;
Expand Down
7 changes: 3 additions & 4 deletions src/main/webapp/app/components/tabs/ReviewHistoryTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import { onValue, ref } from 'firebase/database';
import React, { useEffect, useState } from 'react';
import { Button, Input, Label, Spinner } from 'reactstrap';

export interface IReviewHistoryTab extends StoreProps {
isGermline: boolean;
}
export interface IReviewHistoryTab extends StoreProps {}

function ReviewHistoryTab({ isGermline, firebaseDb, drugList, getDrugs }: IReviewHistoryTab) {
const [historyCollection, setHistoryCollection] = useState<HistoryCollection>();
Expand Down Expand Up @@ -90,10 +88,11 @@ function ReviewHistoryTab({ isGermline, firebaseDb, drugList, getDrugs }: IRevie
);
}

const mapStoreToProps = ({ firebaseAppStore, drugStore }: IRootStore) => ({
const mapStoreToProps = ({ firebaseAppStore, drugStore, routerStore }: IRootStore) => ({
firebaseDb: firebaseAppStore.firebaseDb,
drugList: drugStore.entities,
getDrugs: drugStore.getEntities,
isGermline: routerStore.isGermline,
});

type StoreProps = Partial<ReturnType<typeof mapStoreToProps>>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';

const GeneAliasesDescription: React.FunctionComponent<{
geneAliases: string[];
className?: string;
style?: React.CSSProperties;
}> = props => {
return <span className={props.className} style={props.style}>{`Also known as ${props.geneAliases.join(', ')}`}</span>;
};
export default GeneAliasesDescription;
12 changes: 10 additions & 2 deletions src/main/webapp/app/pages/curation/BadgeGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,19 @@ const BadgeGroup = (props: IBadgeGroupProps) => {
}, [sectionData, props.firebasePath]);

if (props.showDemotedBadge) {
return <DefaultBadge color="danger" text="Demoted" tooltipOverlay={DEMOTED_MUTATION_TOOLTIP_OVERLAY} />;
return (
<DefaultBadge color="danger" tooltipOverlay={DEMOTED_MUTATION_TOOLTIP_OVERLAY}>
Demoted
</DefaultBadge>
);
}

if (props.showDeletedBadge) {
return <DefaultBadge color="danger" text="Deleted" tooltipOverlay={DELETED_SECTION_TOOLTIP_OVERLAY} />;
return (
<DefaultBadge color="danger" tooltipOverlay={DELETED_SECTION_TOOLTIP_OVERLAY}>
Deleted
</DefaultBadge>
);
}

if (props.showNotCuratableBadge?.show) {
Expand Down
28 changes: 14 additions & 14 deletions src/main/webapp/app/pages/curation/CurationPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import { IRootStore } from 'app/stores';
import { RouteComponentProps } from 'react-router-dom';
import { getFirebaseGenePath, getFirebaseHistoryPath, getFirebaseMetaGenePath } from 'app/shared/util/firebase/firebase-utils';
import { Col, Row } from 'reactstrap';
import { getSectionClassName } from 'app/shared/util/utils';
import { GENE_TYPE, GENE_TYPE_KEY, INHERITANCE_MECHANISM_OPTIONS, READABLE_FIELD, PENETRANCE_OPTIONS } from 'app/config/constants/firebase';
import { GERMLINE_PATH, GET_ALL_DRUGS_PAGE_SIZE, RADIO_OPTION_NONE } from 'app/config/constants/constants';
import { GET_ALL_DRUGS_PAGE_SIZE, RADIO_OPTION_NONE } from 'app/config/constants/constants';
import CommentIcon from 'app/shared/icons/CommentIcon';
import GeneHistoryTooltip from 'app/components/geneHistoryTooltip/GeneHistoryTooltip';
import MutationsSection from './mutation/MutationsSection';
Expand All @@ -24,16 +23,17 @@ import GeneRealtimeComponentHeader from './header/GeneRealtimeComponentHeader';
import RelevantCancerTypesModal from 'app/shared/modal/RelevantCancerTypesModal';
import { notifyError } from 'app/oncokb-commons/components/util/NotificationUtils';
import LoadingIndicator, { LoaderSize } from 'app/oncokb-commons/components/loadingIndicator/LoadingIndicator';
import { FlattenedHistory, parseHistory } from 'app/shared/util/firebase/firebase-history-utils';
import { parseHistory } from 'app/shared/util/firebase/firebase-history-utils';
import { useMatchGeneEntity } from 'app/hooks/useMatchGeneEntity';
import { Unsubscribe } from 'firebase/database';
import { getLocationIdentifier, getTooltipHistoryList } from 'app/components/geneHistoryTooltip/gene-history-tooltip-utils';
import GeneticTypeTabs, { GENETIC_TYPE } from './geneticTypeTabs/GeneticTypeTabs';
import GeneticTypeTabHeader from './header/GeneticTypeTabHeader';

export interface ICurationPageProps extends StoreProps, RouteComponentProps<{ hugoSymbol: string }> {}

export const CurationPage = (props: ICurationPageProps) => {
const pathname = props.location.pathname;
const isGermline = pathname.includes(GERMLINE_PATH);
const isGermline = props.isGermline;
const hugoSymbolParam = props.match.params.hugoSymbol;

const [mutationListRendered, setMutationListRendered] = useState(false);
Expand Down Expand Up @@ -85,18 +85,16 @@ export const CurationPage = (props: ICurationPageProps) => {
return getTooltipHistoryList(tabHistoryList);
}, [tabHistoryList]);

return props.firebaseInitSuccess && !props.loadingGenes && props.drugList.length > 0 && !!geneEntity ? (
return props.firebaseInitSuccess && !props.loadingGenes && props.drugList.length > 0 && !!geneEntity && hugoSymbol ? (
<>
<div style={{ visibility: mutationListRendered ? 'visible' : 'hidden' }}>
<GeneHeader
hugoSymbol={hugoSymbol}
firebaseGenePath={firebaseGenePath}
geneEntity={geneEntity}
isGermline={isGermline}
isReviewing={false}
/>
<GeneHeader firebaseGenePath={firebaseGenePath} geneEntity={geneEntity} isReviewing={false} />
<GeneticTypeTabs geneEntity={geneEntity} geneticType={isGermline ? GENETIC_TYPE.GERMLINE : GENETIC_TYPE.SOMATIC} />
<div className="d-flex justify-content-end mt-2 mb-2">
<GeneticTypeTabHeader hugoSymbol={hugoSymbol} isReviewing={false} />
</div>
<div className="mb-4">
<Row className={`${getSectionClassName()} justify-content-between`}>
<Row className={'justify-content-between'}>
<Col className="pb-2">
<RealtimeCheckedInputGroup
groupHeader={
Expand Down Expand Up @@ -282,6 +280,7 @@ const mapStoreToProps = ({
firebaseGeneService,
openMutationCollapsibleStore,
layoutStore,
routerStore,
}: IRootStore) => ({
firebaseDb: firebaseAppStore.firebaseDb,
firebaseInitSuccess: firebaseAppStore.firebaseInitSuccess,
Expand All @@ -298,6 +297,7 @@ const mapStoreToProps = ({
updateRelevantCancerTypes: firebaseGeneService.updateRelevantCancerTypes,
setOpenMutationCollapsibleIndex: openMutationCollapsibleStore.setOpenMutationCollapsibleIndex,
toggleOncoKBSidebar: layoutStore.toggleOncoKBSidebar,
isGermline: routerStore.isGermline,
});

type StoreProps = ReturnType<typeof mapStoreToProps>;
Expand Down
13 changes: 6 additions & 7 deletions src/main/webapp/app/pages/curation/GeneListPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Col, Row } from 'reactstrap';
import LoadingIndicator, { LoaderSize } from 'app/oncokb-commons/components/loadingIndicator/LoadingIndicator';
import { createGeneIfDoesNotExist, geneNeedsReview } from 'app/shared/util/firebase/firebase-utils';
import { Link, RouteComponentProps, generatePath, useHistory } from 'react-router-dom';
import { APP_DATETIME_FORMAT, GERMLINE_PATH, PAGE_ROUTE } from 'app/config/constants/constants';
import { APP_DATETIME_FORMAT, PAGE_ROUTE } from 'app/config/constants/constants';
import OncoKBTable, { SearchColumn } from 'app/shared/table/OncoKBTable';
import { filterByKeyword } from 'app/shared/util/utils';
import { TextFormat } from 'react-jhipster';
Expand Down Expand Up @@ -35,9 +35,7 @@ export interface IGeneListPage extends StoreProps, RouteComponentProps {}

const GeneListPage = (props: IGeneListPage) => {
const history = useHistory();

const pathname = props.location.pathname;
const isGermline = pathname.includes(GERMLINE_PATH);
const isGermline = props.isGermline;

useEffect(() => {
if (props.firebaseReady) {
Expand Down Expand Up @@ -117,7 +115,7 @@ const GeneListPage = (props: IGeneListPage) => {
const tabs = [
{
title: 'Tools',
content: <GeneListPageToolsTab metaData={props.metaData} isGermline={isGermline} />,
content: <GeneListPageToolsTab metaData={props.metaData} />,
},
] as Tab[];
if (!isGermline) {
Expand All @@ -133,7 +131,7 @@ const GeneListPage = (props: IGeneListPage) => {
});
tabs.push({
title: 'History',
content: <ReviewHistoryTab isGermline={isGermline} />,
content: <ReviewHistoryTab />,
});
return tabs;
}, [props.metaData]);
Expand Down Expand Up @@ -183,14 +181,15 @@ const GeneListPage = (props: IGeneListPage) => {
);
};

const mapStoreToProps = ({ firebaseMetaStore, firebaseAppStore, firebaseGeneService }: IRootStore) => ({
const mapStoreToProps = ({ firebaseMetaStore, firebaseAppStore, firebaseGeneService, routerStore }: IRootStore) => ({
firebaseDb: firebaseAppStore.firebaseDb,
firebaseReady: firebaseAppStore.firebaseReady,
firebaseInitError: firebaseAppStore.firebaseInitError,
firebaseLoginError: firebaseAppStore.firebaseLoginError,
addMetaListener: firebaseMetaStore.addListener,
metaData: firebaseMetaStore.data,
createGene: firebaseGeneService.createGene,
isGermline: routerStore.isGermline,
});

type StoreProps = ReturnType<typeof mapStoreToProps>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,9 @@ export const ReviewCollapsible = ({
if (isUnderCreationOrDeletion) {
return undefined;
}
return <DefaultBadge color={ReviewCollapsibleBootstrapClass[reviewAction ?? '']} text={ReviewActionLabels[reviewAction ?? '']} />;
return (
<DefaultBadge color={ReviewCollapsibleBootstrapClass[reviewAction ?? '']}>{ReviewActionLabels[reviewAction ?? '']}</DefaultBadge>
);
};

const getReviewableContent = () => {
Expand Down
141 changes: 141 additions & 0 deletions src/main/webapp/app/pages/curation/geneticTypeTabs/GeneticTypeTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { useEffect, useMemo, useState } from 'react';
import * as styles from './genetic-type-tabs.module.scss';
import classnames from 'classnames';
import { componentInject } from 'app/shared/util/typed-inject';
import { observer } from 'mobx-react';
import { IRootStore } from 'app/stores';
import { IGene } from 'app/shared/model/gene.model';
import DefaultBadge from 'app/shared/badge/DefaultBadge';
import { createGeneIfDoesNotExist, geneMetaReviewHasUuids, getFirebaseMetaGenePath } from 'app/shared/util/firebase/firebase-utils';
import { GERMLINE_PATH, SOMATIC_GERMLINE_SETTING_KEY, SOMATIC_PATH } from 'app/config/constants/constants';
import { notifyError } from 'app/oncokb-commons/components/util/NotificationUtils';
import { onValue, ref, Unsubscribe } from 'firebase/database';
import { useLocation } from 'react-router-dom';
import { MetaReview } from 'app/shared/model/firebase/firebase.model';

export enum GENETIC_TYPE {
SOMATIC = 'somatic',
GERMLINE = 'germline',
}

export interface IGeneticTypeTabs extends StoreProps {
geneEntity: IGene;
geneticType: GENETIC_TYPE;
}

const GeneticTypeTabs = ({ geneEntity, geneticType, firebaseDb, createGene }: IGeneticTypeTabs) => {
const { pathname } = useLocation();
const [selected, setSelected] = useState<GENETIC_TYPE>(geneticType || GENETIC_TYPE.SOMATIC);
const [somaticMetaReview, setSomaticMetaReview] = useState<MetaReview>();
const [germlineMetaReview, setGermlineMetaReview] = useState<MetaReview>();

const currentVariantType = selected === GENETIC_TYPE.SOMATIC ? SOMATIC_PATH : GERMLINE_PATH;
const newVariantType = selected === GENETIC_TYPE.SOMATIC ? GERMLINE_PATH : SOMATIC_PATH;

useEffect(() => {
if (!firebaseDb) {
return;
}
const callbacks = [] as Unsubscribe[];
callbacks.push(
onValue(ref(firebaseDb, `${getFirebaseMetaGenePath(false, geneEntity.hugoSymbol)}/review`), snapshot => {
setSomaticMetaReview(snapshot.val());
}),
);
callbacks.push(
onValue(ref(firebaseDb, `${getFirebaseMetaGenePath(true, geneEntity.hugoSymbol)}/review`), snapshot => {
setGermlineMetaReview(snapshot.val());
}),
);
return () => callbacks.forEach(callback => callback?.());
}, []);

const geneReleaseStatus = useMemo(() => {
const somaticStatus = !!geneEntity?.flags?.find(flag => flag.flag === 'ONCOKB_SOMATIC' && flag.name === 'OncoKB Somatic');
const germlineStatus = !!geneEntity?.flags?.find(flag => flag.flag === 'ONCOKB_GERMLINE' && flag.name === 'OncoKB Germline');
return {
[GENETIC_TYPE.SOMATIC]: somaticStatus,
[GENETIC_TYPE.GERMLINE]: germlineStatus,
};
}, [geneEntity]);

const getGeneReleaseStatusBadge = (type: GENETIC_TYPE) => {
const badges: JSX.Element[] = [];
const sharedClassname = 'ms-2';
const sharedStyle: React.CSSProperties = { fontSize: '0.8rem' };

const needsReview = {
[GENETIC_TYPE.SOMATIC]: geneMetaReviewHasUuids(somaticMetaReview),
[GENETIC_TYPE.GERMLINE]: geneMetaReviewHasUuids(germlineMetaReview),
};
if (needsReview[type]) {
badges.push(
<DefaultBadge square color={'warning'} className={sharedClassname} style={sharedStyle}>
Needs Review
</DefaultBadge>,
);
}

const isGeneReleased = geneReleaseStatus[type];
if (isGeneReleased) {
// Todo: In tooltip show when gene was released
badges.push(
<DefaultBadge square color="success" className={sharedClassname} style={sharedStyle}>
Released
</DefaultBadge>,
);
} else {
badges.push(
<DefaultBadge square color={'warning'} className={sharedClassname} style={sharedStyle}>
Pending Release
</DefaultBadge>,
);
}

return <>{badges.map(badge => badge)}</>;
};

async function handleToggle() {
if (!firebaseDb) {
return;
}
if (geneEntity.hugoSymbol && createGene) {
// On curation page
try {
await createGeneIfDoesNotExist(geneEntity.hugoSymbol, newVariantType === 'germline', firebaseDb, createGene);
} catch (error) {
notifyError(error);
}
}
localStorage.setItem(SOMATIC_GERMLINE_SETTING_KEY, newVariantType);
window.location.href = pathname.replace(currentVariantType, newVariantType);
}

return (
<div className={styles.tabs}>
{[GENETIC_TYPE.SOMATIC, GENETIC_TYPE.GERMLINE].map((geneOrigin, idx) => (
<div
key={idx}
style={{ width: '50%' }}
className={
selected === geneOrigin ? classnames(styles.tab, styles.selectedTab, 'fw-bold') : classnames(styles.tab, styles.unselectedTab)
}
onClick={handleToggle}
>
{geneOrigin.substring(0, 1).toUpperCase()}
{geneOrigin.toLowerCase().slice(1)}
{getGeneReleaseStatusBadge(geneOrigin)}
</div>
))}
</div>
);
};

const mapStoreToProps = ({ firebaseAppStore, firebaseGeneService }: IRootStore) => ({
firebaseDb: firebaseAppStore.firebaseDb,
createGene: firebaseGeneService.createGene,
});

type StoreProps = Partial<ReturnType<typeof mapStoreToProps>>;

export default componentInject(mapStoreToProps)(observer(GeneticTypeTabs));
Loading

0 comments on commit d3350ea

Please sign in to comment.