diff --git a/src/main/webapp/app/pages/curation/collapsible/CancerTypeCollapsible.tsx b/src/main/webapp/app/pages/curation/collapsible/CancerTypeCollapsible.tsx index e7d00b158..4793314b6 100644 --- a/src/main/webapp/app/pages/curation/collapsible/CancerTypeCollapsible.tsx +++ b/src/main/webapp/app/pages/curation/collapsible/CancerTypeCollapsible.tsx @@ -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'; diff --git a/src/main/webapp/app/pages/curation/collapsible/MutationCollapsible.tsx b/src/main/webapp/app/pages/curation/collapsible/MutationCollapsible.tsx index c329dd3ea..1b91f1547 100644 --- a/src/main/webapp/app/pages/curation/collapsible/MutationCollapsible.tsx +++ b/src/main/webapp/app/pages/curation/collapsible/MutationCollapsible.tsx @@ -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'; @@ -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; @@ -77,15 +78,21 @@ const MutationCollapsible = ({ annotatedAltsCache, genomicIndicators, showLastModified, + handleFirebaseUpdate, }: IMutationCollapsibleProps) => { const firebaseMutationsPath = `${getFirebaseGenePath(isGermline, hugoSymbol)}/mutations`; const [mutationUuid, setMutationUuid] = useState(''); const [mutationName, setMutationName] = useState(''); const [mutationNameReview, setMutationNameReview] = useState(null); + const [mutationSummary, setMutationSummary] = useState(''); const [mutationAlterations, setMutationAlterations] = useState(null); const [isRemovableWithoutReview, setIsRemovableWithoutReview] = useState(false); const [relatedAnnotationResult, setRelatedAnnotationResult] = useState([]); + const [oncogenicity, setOncogenicity] = useState(''); + const [showSimpleConfirmModal, setShowSimpleConfirmModal] = useState(false); + const [simpleConfirmModalBody, setSimpleConfirmModalBody] = useState(undefined); + const [mutationSummaryRef, setMutationSummaryRef] = useState(null); useEffect(() => { const arr = annotatedAltsCache?.get(hugoSymbol ?? '', [{ name: mutationName, alterations: mutationAlterations }]) ?? []; @@ -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()); @@ -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`), @@ -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 | React.MouseEvent | React.MouseEvent, + ) { + 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; @@ -308,6 +353,25 @@ const MutationCollapsible = ({ } isPendingDelete={isMutationPendingDelete} > + + } + name="mutationSummary" + parseRefs + disabled={oncogenicity === ''} + disabledMessage={'Not curatable: mutation summary is only curatable when oncogenicity is specified.'} + /> } + /** 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, @@ -598,6 +666,12 @@ const MutationCollapsible = ({ }} /> ) : undefined} + setShowSimpleConfirmModal(false)} + /> ); }; @@ -623,6 +697,7 @@ const mapStoreToProps = ({ firebaseDb: firebaseAppStore.firebaseDb, annotatedAltsCache: curationPageStore.annotatedAltsCache, genomicIndicators: firebaseGenomicIndicatorsStore.data, + handleFirebaseUpdate: firebaseGeneService.updateObject, }); type StoreProps = Partial>; diff --git a/src/main/webapp/app/shared/firebase/input/RealtimeBasicInput.tsx b/src/main/webapp/app/shared/firebase/input/RealtimeBasicInput.tsx index b2d1451cf..b7d619c94 100644 --- a/src/main/webapp/app/shared/firebase/input/RealtimeBasicInput.tsx +++ b/src/main/webapp/app/shared/firebase/input/RealtimeBasicInput.tsx @@ -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'; @@ -51,6 +51,7 @@ export interface IRealtimeBasicInput extends React.InputHTMLAttributes; invalid?: boolean; invalidMessage?: string; labelClass?: string; @@ -58,6 +59,7 @@ export interface IRealtimeBasicInput extends React.InputHTMLAttributes = (props: IRealtimeBasicInput) => { @@ -79,6 +81,11 @@ const RealtimeBasicInput: React.FunctionComponent = (props: updateReviewableContent, style, updateMetaData, + placeholder, + disabled, + disabledMessage, + onMouseDown, + labelOnClick, ...otherProps } = props; @@ -139,7 +146,7 @@ const RealtimeBasicInput: React.FunctionComponent = (props: }, [inputValueLoaded]); const labelComponent = label && ( - + ); const inputChangeHandler = (e: React.ChangeEvent) => { @@ -169,22 +176,26 @@ const RealtimeBasicInput: React.FunctionComponent = (props: <> { 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} + {disabled && disabledMessage && inputValue &&
{disabledMessage}
} {invalid && {invalidMessage || ''}} ); diff --git a/src/main/webapp/app/shared/firebase/input/RealtimeInputs.tsx b/src/main/webapp/app/shared/firebase/input/RealtimeInputs.tsx index f46550761..a6ff59bae 100644 --- a/src/main/webapp/app/shared/firebase/input/RealtimeInputs.tsx +++ b/src/main/webapp/app/shared/firebase/input/RealtimeInputs.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { MouseEventHandler } from 'react'; import RealtimeBasicInput, { IRealtimeBasicInput, RealtimeInputType } from './RealtimeBasicInput'; /** @@ -40,6 +40,9 @@ export interface IRealtimeCheckedInputGroup { inlineHeader?: boolean; options: RealtimeCheckedInputOption[]; disabled?: boolean; + onClick?: MouseEventHandler; + onMouseDown?: MouseEventHandler; + labelOnClick?: MouseEventHandler; } export const RealtimeCheckedInputGroup = (props: IRealtimeCheckedInputGroup) => { @@ -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}`} /> ) : ( @@ -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} /> ); diff --git a/src/main/webapp/app/shared/model/firebase/firebase.model.ts b/src/main/webapp/app/shared/model/firebase/firebase.model.ts index e8dcd0895..c231392fd 100644 --- a/src/main/webapp/app/shared/model/firebase/firebase.model.ts +++ b/src/main/webapp/app/shared/model/firebase/firebase.model.ts @@ -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(); diff --git a/src/main/webapp/app/shared/util/firebase/firebase-utils.tsx b/src/main/webapp/app/shared/util/firebase/firebase-utils.tsx index 6c9cf5ca0..ef2cddc36 100644 --- a/src/main/webapp/app/shared/util/firebase/firebase-utils.tsx +++ b/src/main/webapp/app/shared/util/firebase/firebase-utils.tsx @@ -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']; diff --git a/src/test/javascript/screenshots/baseline/desktop_chrome/mutation-effect-not-curatable--1920x1080.png b/src/test/javascript/screenshots/baseline/desktop_chrome/mutation-effect-not-curatable--1920x1080.png index d67a2deb1..7fc9adc4c 100644 Binary files a/src/test/javascript/screenshots/baseline/desktop_chrome/mutation-effect-not-curatable--1920x1080.png and b/src/test/javascript/screenshots/baseline/desktop_chrome/mutation-effect-not-curatable--1920x1080.png differ diff --git a/src/test/javascript/screenshots/baseline/desktop_chrome/open-therapy-collapsible--1920x1080.png b/src/test/javascript/screenshots/baseline/desktop_chrome/open-therapy-collapsible--1920x1080.png index 3ef61b63a..922142bb6 100644 Binary files a/src/test/javascript/screenshots/baseline/desktop_chrome/open-therapy-collapsible--1920x1080.png and b/src/test/javascript/screenshots/baseline/desktop_chrome/open-therapy-collapsible--1920x1080.png differ diff --git a/src/test/javascript/screenshots/baseline/desktop_chrome/relevant-cancer-types-modal--1920x1080.png b/src/test/javascript/screenshots/baseline/desktop_chrome/relevant-cancer-types-modal--1920x1080.png index cf2ed35f2..18a41e15e 100644 Binary files a/src/test/javascript/screenshots/baseline/desktop_chrome/relevant-cancer-types-modal--1920x1080.png and b/src/test/javascript/screenshots/baseline/desktop_chrome/relevant-cancer-types-modal--1920x1080.png differ