Skip to content

Commit

Permalink
Allow curating mutation summary (#433)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhx828 authored Sep 25, 2024
1 parent f3e0d76 commit 0d93628
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { isSectionRemovableWithoutReview } from 'app/shared/util/firebase/fireba
import EditIcon from 'app/shared/icons/EditIcon';
import ModifyCancerTypeModal from 'app/shared/modal/ModifyCancerTypeModal';
import { notifyError } from 'app/oncokb-commons/components/util/NotificationUtils';
import _ from 'lodash';
import { getLevelDropdownOptions } from 'app/shared/util/firebase/firebase-level-utils';
import { DIAGNOSTIC_LEVELS_ORDERING, READABLE_FIELD, PROGNOSTIC_LEVELS_ORDERING } from 'app/config/constants/firebase';
import { RealtimeTextAreaInput } from 'app/shared/firebase/input/RealtimeInputs';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { IRootStore } from 'app/stores';
import { get, onValue, ref } from 'firebase/database';
import _ from 'lodash';
import { observer } from 'mobx-react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Button } from 'reactstrap';
import BadgeGroup from '../BadgeGroup';
import { DeleteSectionButton } from '../button/DeleteSectionButton';
Expand All @@ -49,6 +49,7 @@ import { NestLevelColor, NestLevelMapping, NestLevelType } from './NestLevel';
import { RemovableCollapsible } from './RemovableCollapsible';
import { Unsubscribe } from 'firebase/database';
import { getLocationIdentifier } from 'app/components/geneHistoryTooltip/gene-history-tooltip-utils';
import { SimpleConfirmModal } from 'app/shared/modal/SimpleConfirmModal';

export interface IMutationCollapsibleProps extends StoreProps {
mutationPath: string;
Expand Down Expand Up @@ -77,15 +78,21 @@ const MutationCollapsible = ({
annotatedAltsCache,
genomicIndicators,
showLastModified,
handleFirebaseUpdate,
}: IMutationCollapsibleProps) => {
const firebaseMutationsPath = `${getFirebaseGenePath(isGermline, hugoSymbol)}/mutations`;

const [mutationUuid, setMutationUuid] = useState<string>('');
const [mutationName, setMutationName] = useState<string>('');
const [mutationNameReview, setMutationNameReview] = useState<Review | null>(null);
const [mutationSummary, setMutationSummary] = useState<string>('');
const [mutationAlterations, setMutationAlterations] = useState<Alteration[] | null>(null);
const [isRemovableWithoutReview, setIsRemovableWithoutReview] = useState(false);
const [relatedAnnotationResult, setRelatedAnnotationResult] = useState<AlterationAnnotationStatus[]>([]);
const [oncogenicity, setOncogenicity] = useState<string>('');
const [showSimpleConfirmModal, setShowSimpleConfirmModal] = useState<boolean>(false);
const [simpleConfirmModalBody, setSimpleConfirmModalBody] = useState<string | undefined>(undefined);
const [mutationSummaryRef, setMutationSummaryRef] = useState<HTMLElement | null>(null);

useEffect(() => {
const arr = annotatedAltsCache?.get(hugoSymbol ?? '', [{ name: mutationName, alterations: mutationAlterations }]) ?? [];
Expand Down Expand Up @@ -157,6 +164,11 @@ const MutationCollapsible = ({
setMutationName(snapshot.val());
}),
);
callbacks.push(
onValue(ref(firebaseDb, `${mutationPath}/summary`), snapshot => {
setMutationSummary(snapshot.val());
}),
);
callbacks.push(
onValue(ref(firebaseDb, `${mutationPath}/alterations`), snapshot => {
setMutationAlterations(snapshot.val());
Expand All @@ -169,6 +181,11 @@ const MutationCollapsible = ({
setIsRemovableWithoutReview(isSectionRemovableWithoutReview(review));
}),
);
callbacks.push(
onValue(ref(firebaseDb, `${mutationPath}/mutation_effect/oncogenic`), snapshot => {
setOncogenicity(snapshot.val());
}),
);

onValue(
ref(firebaseDb, `${mutationPath}/name_uuid`),
Expand Down Expand Up @@ -197,6 +214,34 @@ const MutationCollapsible = ({
[mutationPath, mutationName, parsedHistoryList],
);

async function simpleConfirmModalOnConfirm() {
await handleFirebaseUpdate?.(mutationPath, { summary: '' });
if (mutationSummaryRef) {
mutationSummaryRef.click();
}
setShowSimpleConfirmModal(false);
setSimpleConfirmModalBody(undefined);
}

function oncogenicityRadioOnClick(
event: React.MouseEvent<HTMLInputElement> | React.MouseEvent<HTMLLabelElement> | React.MouseEvent<HTMLDivElement>,
) {
if (mutationSummary && event.target) {
let newOncogenicityVal;
if (event.target instanceof HTMLInputElement) {
newOncogenicityVal = event.target.value;
} else if (event.target instanceof HTMLDivElement || event.target instanceof HTMLLabelElement) {
newOncogenicityVal = event.target.innerText;
}
if (newOncogenicityVal === RADIO_OPTION_NONE) {
event.preventDefault();
setMutationSummaryRef(event.target as HTMLElement);
setShowSimpleConfirmModal(true);
setSimpleConfirmModalBody(`Mutation summary will be removed after removing oncogenicity.`);
}
}
}

async function handleDeleteMutation(toVus = false) {
if (!firebaseDb) {
return;
Expand Down Expand Up @@ -308,6 +353,25 @@ const MutationCollapsible = ({
}
isPendingDelete={isMutationPendingDelete}
>
<RealtimeTextAreaInput
firebasePath={`${mutationPath}/summary`}
inputClass={styles.summaryTextarea}
label="Mutation Summary (Optional)"
labelIcon={
<GeneHistoryTooltip
historyData={parsedHistoryList}
location={`${getMutationName(mutationName, mutationAlterations)}, ${READABLE_FIELD.SUMMARY}`}
locationIdentifier={getLocationIdentifier({
mutationUuid,
fields: [READABLE_FIELD.SUMMARY],
})}
/>
}
name="mutationSummary"
parseRefs
disabled={oncogenicity === ''}
disabledMessage={'Not curatable: mutation summary is only curatable when oncogenicity is specified.'}
/>
<Collapsible
idPrefix={`${mutationName}-mutation-effect`}
title="Mutation Effect"
Expand Down Expand Up @@ -369,6 +433,10 @@ const MutationCollapsible = ({
}
</>
}
/** Radio a bit tricky. Have to use onMouseDown event to cancel the default event.
* The onclick event does not like to be overwritten **/
onMouseDown={oncogenicityRadioOnClick}
labelOnClick={oncogenicityRadioOnClick}
isRadio
options={[...ONCOGENICITY_OPTIONS, RADIO_OPTION_NONE].map(label => ({
label,
Expand Down Expand Up @@ -598,6 +666,12 @@ const MutationCollapsible = ({
}}
/>
) : undefined}
<SimpleConfirmModal
show={showSimpleConfirmModal}
body={simpleConfirmModalBody}
onConfirm={simpleConfirmModalOnConfirm}
onCancel={() => setShowSimpleConfirmModal(false)}
/>
</>
);
};
Expand All @@ -623,6 +697,7 @@ const mapStoreToProps = ({
firebaseDb: firebaseAppStore.firebaseDb,
annotatedAltsCache: curationPageStore.annotatedAltsCache,
genomicIndicators: firebaseGenomicIndicatorsStore.data,
handleFirebaseUpdate: firebaseGeneService.updateObject,
});

type StoreProps = Partial<ReturnType<typeof mapStoreToProps>>;
Expand Down
17 changes: 14 additions & 3 deletions src/main/webapp/app/shared/firebase/input/RealtimeBasicInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { IRootStore } from 'app/stores';
import { default as classNames, default as classnames } from 'classnames';
import { onValue, ref } from 'firebase/database';
import { inject } from 'mobx-react';
import React, { useEffect, useRef, useState } from 'react';
import React, { MouseEventHandler, useEffect, useRef, useState } from 'react';
import { FormFeedback, Input, Label, LabelProps } from 'reactstrap';
import { InputType } from 'reactstrap/types/lib/Input';
import * as styles from './styles.module.scss';
Expand Down Expand Up @@ -51,13 +51,15 @@ export interface IRealtimeBasicInput extends React.InputHTMLAttributes<HTMLInput
firebasePath: string; // firebase path that component needs to listen to
type: RealtimeBasicInputType;
label: string;
labelOnClick?: MouseEventHandler<HTMLLabelElement>;
invalid?: boolean;
invalidMessage?: string;
labelClass?: string;
labelIcon?: JSX.Element;
inputClass?: string;
parseRefs?: boolean;
updateMetaData?: boolean;
disabledMessage?: string;
}

const RealtimeBasicInput: React.FunctionComponent<IRealtimeBasicInput> = (props: IRealtimeBasicInput) => {
Expand All @@ -79,6 +81,11 @@ const RealtimeBasicInput: React.FunctionComponent<IRealtimeBasicInput> = (props:
updateReviewableContent,
style,
updateMetaData,
placeholder,
disabled,
disabledMessage,
onMouseDown,
labelOnClick,
...otherProps
} = props;

Expand Down Expand Up @@ -138,7 +145,7 @@ const RealtimeBasicInput: React.FunctionComponent<IRealtimeBasicInput> = (props:
}, [inputValueLoaded]);

const labelComponent = label && (
<RealtimeBasicLabel label={label} labelIcon={labelIcon} id={id} labelClass={isCheckType ? 'mb-0' : 'fw-bold'} />
<RealtimeBasicLabel label={label} labelIcon={labelIcon} id={id} labelClass={isCheckType ? 'mb-0' : 'fw-bold'} onClick={labelOnClick} />
);

const inputChangeHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
Expand Down Expand Up @@ -178,22 +185,26 @@ const RealtimeBasicInput: React.FunctionComponent<IRealtimeBasicInput> = (props:
<>
<Input
innerRef={inputRef}
className={classNames(inputClass, isCheckType && 'ms-1 position-relative', isTextType && styles.editableTextBox)}
className={classNames(inputClass, isCheckType && 'ms-1 position-relative', isTextType && !props.disabled && styles.editableTextBox)}
id={id}
name={`${id}-${label.toLowerCase()}`}
autoComplete="off"
onChange={e => {
inputChangeHandler(e);
}}
onMouseDown={onMouseDown}
type={props.type as InputType}
style={inputStyle}
value={inputValue}
invalid={invalid}
checked={isCheckType && isChecked()}
disabled={disabled}
placeholder={placeholder ? placeholder : disabled && disabledMessage ? disabledMessage : ''}
{...otherProps}
>
{children}
</Input>
{disabled && disabledMessage && inputValue && <div className={'text-danger'}>{disabledMessage}</div>}
{invalid && <FormFeedback>{invalidMessage || ''}</FormFeedback>}
</>
);
Expand Down
10 changes: 9 additions & 1 deletion src/main/webapp/app/shared/firebase/input/RealtimeInputs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { MouseEventHandler } from 'react';
import RealtimeBasicInput, { IRealtimeBasicInput, RealtimeInputType } from './RealtimeBasicInput';

/**
Expand Down Expand Up @@ -40,6 +40,9 @@ export interface IRealtimeCheckedInputGroup {
inlineHeader?: boolean;
options: RealtimeCheckedInputOption[];
disabled?: boolean;
onClick?: MouseEventHandler<HTMLInputElement>;
onMouseDown?: MouseEventHandler<HTMLInputElement>;
labelOnClick?: MouseEventHandler<HTMLLabelElement>;
}

export const RealtimeCheckedInputGroup = (props: IRealtimeCheckedInputGroup) => {
Expand All @@ -54,7 +57,10 @@ export const RealtimeCheckedInputGroup = (props: IRealtimeCheckedInputGroup) =>
key={option.label}
firebasePath={option.firebasePath}
className="me-2"
value={option.label}
label={option.label}
onMouseDown={props.onMouseDown}
labelOnClick={props.labelOnClick}
id={`${option.firebasePath}-${option.label}`}
/>
) : (
Expand All @@ -64,6 +70,8 @@ export const RealtimeCheckedInputGroup = (props: IRealtimeCheckedInputGroup) =>
firebasePath={option.firebasePath}
style={{ marginTop: '0.1rem' }}
className="me-2"
onMouseDown={props.onMouseDown}
labelOnClick={props.labelOnClick}
label={option.label}
/>
);
Expand Down
3 changes: 3 additions & 0 deletions src/main/webapp/app/shared/model/firebase/firebase.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,9 @@ export class Mutation {
name_uuid: string = generateUuid();
tumors: Tumor[] = [];
tumors_uuid: string = generateUuid();
summary = '';
summary_review?: Review;
summary_uuid: string = generateUuid();

// Germline
mutation_specific_penetrance = new MutationSpecificPenetrance();
Expand Down
6 changes: 5 additions & 1 deletion src/main/webapp/app/shared/util/firebase/firebase-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,12 @@ const addDuplicateMutationInfo = (duplicates: DuplicateMutationInfo[], mutationN
}
};

export const hasMultipleMutations = (mutationName: string) => {
return mutationName.includes(',');
};
export const isMutationEffectCuratable = (mutationName: string) => {
if (mutationName.includes(',')) {
const multipleMuts = hasMultipleMutations(mutationName);
if (multipleMuts) {
return false;
}
const excludedMutations = ['Oncogenic Mutations'];
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0d93628

Please sign in to comment.