Skip to content

Commit 459720a

Browse files
authored
Fix immediate deletion logic (#332)
1 parent 5d7bfbb commit 459720a

File tree

7 files changed

+113
-44
lines changed

7 files changed

+113
-44
lines changed

src/main/webapp/app/pages/curation/collapsible/CancerTypeCollapsible.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CancerType, Review } from 'app/shared/model/firebase/firebase.model';
22
import { componentInject } from 'app/shared/util/typed-inject';
33
import { getCancerTypesNameWithExclusion } from 'app/shared/util/utils';
44
import { IRootStore } from 'app/stores';
5-
import { onValue, ref } from 'firebase/database';
5+
import { get, onValue, ref } from 'firebase/database';
66
import { observer } from 'mobx-react';
77
import React, { useEffect, useState } from 'react';
88
import GeneHistoryTooltip from 'app/components/geneHistoryTooltip/GeneHistoryTooltip';
@@ -81,6 +81,11 @@ function CancerTypeCollapsible({
8181
return () => callbacks.forEach(callback => callback?.());
8282
}, [cancerTypePath, firebaseDb]);
8383

84+
async function handleDeleteCancerType() {
85+
const snapshot = await get(ref(firebaseDb, cancerTypePath));
86+
deleteSection(`${cancerTypePath}/cancerTypes`, snapshot.val(), cancerTypesReview, cancerTypesUuid);
87+
}
88+
8489
if (!cancerTypes || !cancerTypesUuid) {
8590
return <></>;
8691
}
@@ -110,7 +115,7 @@ function CancerTypeCollapsible({
110115
/>
111116
<DeleteSectionButton
112117
sectionName={cancerTypeName}
113-
deleteHandler={() => deleteSection(`${cancerTypePath}/cancerTypes`, cancerTypesReview, cancerTypesUuid)}
118+
deleteHandler={handleDeleteCancerType}
114119
isRemovableWithoutReview={isRemovableWithoutReview}
115120
/>
116121
</>

src/main/webapp/app/pages/curation/collapsible/MutationCollapsible.tsx

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Alteration, Review } from 'app/shared/model/firebase/firebase.model';
2323
import DefaultTooltip from 'app/shared/tooltip/DefaultTooltip';
2424
import { FlattenedHistory } from 'app/shared/util/firebase/firebase-history-utils';
2525
import {
26+
findNestedUuids,
2627
getFirebaseGenePath,
2728
getFirebaseVusPath,
2829
getMutationName,
@@ -32,7 +33,7 @@ import {
3233
import { componentInject } from 'app/shared/util/typed-inject';
3334
import { getExonRanges } from 'app/shared/util/utils';
3435
import { IRootStore } from 'app/stores';
35-
import { onValue, ref } from 'firebase/database';
36+
import { get, onValue, ref } from 'firebase/database';
3637
import _ from 'lodash';
3738
import { observer } from 'mobx-react';
3839
import React, { useEffect, useMemo, useState } from 'react';
@@ -170,12 +171,11 @@ const MutationCollapsible = ({
170171
return () => callbacks.forEach(callback => callback?.());
171172
}, [mutationPath, firebaseDb]);
172173

173-
async function handleDemoteToVus() {
174-
await deleteSection(`${mutationPath}/name`, mutationNameReview, mutationUuid, true);
175-
setIsConvertingToVus(false);
176-
if (open) {
177-
onToggle();
178-
}
174+
async function handleDeleteMutation(toVus = false) {
175+
const snapshot = await get(ref(firebaseDb, mutationPath));
176+
await deleteSection(`${mutationPath}/name`, snapshot.val(), mutationNameReview, mutationUuid, toVus);
177+
if (toVus) setIsConvertingToVus(false);
178+
if (open) onToggle();
179179
}
180180

181181
if (!mutationUuid || !mutationName) {
@@ -247,12 +247,7 @@ const MutationCollapsible = ({
247247
/>
248248
<DeleteSectionButton
249249
sectionName={title}
250-
deleteHandler={async () => {
251-
await deleteSection(`${mutationPath}/name`, mutationNameReview, mutationUuid);
252-
if (open) {
253-
onToggle();
254-
}
255-
}}
250+
deleteHandler={() => handleDeleteMutation()}
256251
isRemovableWithoutReview={isRemovableWithoutReview}
257252
tooltipProps={
258253
isAssociatedWithGenomicIndicator
@@ -531,7 +526,7 @@ const MutationCollapsible = ({
531526
isGermline={isGermline}
532527
vusList={vusData}
533528
onCancel={() => setIsConvertingToVus(false)}
534-
onConfirm={handleDemoteToVus}
529+
onConfirm={() => handleDeleteMutation(true)}
535530
convertOptions={{
536531
initialAlterations: mutationName.split(',').map(alteration => alteration.trim()),
537532
isConverting: true,
@@ -552,6 +547,7 @@ const mapStoreToProps = ({
552547
drugStore,
553548
curationPageStore,
554549
firebaseGenomicIndicatorsStore,
550+
firebaseMutationListStore,
555551
}: IRootStore) => ({
556552
deleteSection: firebaseGeneService.deleteSection,
557553
addTumor: firebaseGeneService.addTumor,
@@ -564,6 +560,7 @@ const mapStoreToProps = ({
564560
firebaseDb: firebaseAppStore.firebaseDb,
565561
annotatedAltsCache: curationPageStore.annotatedAltsCache,
566562
genomicIndicators: firebaseGenomicIndicatorsStore.data,
563+
mutationList: firebaseMutationListStore.data,
567564
});
568565

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

src/main/webapp/app/pages/curation/collapsible/TherapyCollapsible.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { FlattenedHistory } from 'app/shared/util/firebase/firebase-history-util
1010
import { getTxName, isSectionRemovableWithoutReview } from 'app/shared/util/firebase/firebase-utils';
1111
import { componentInject } from 'app/shared/util/typed-inject';
1212
import { IRootStore } from 'app/stores';
13-
import { onValue, ref } from 'firebase/database';
13+
import { get, onValue, ref } from 'firebase/database';
1414
import { observer } from 'mobx-react';
1515
import React, { useEffect, useState } from 'react';
1616
import BadgeGroup from '../BadgeGroup';
@@ -71,6 +71,11 @@ function TherapyCollapsible({
7171
);
7272
}, [therapyPath, firebaseDb]);
7373

74+
async function handleDeleteTherapy() {
75+
const snapshot = await get(ref(firebaseDb, therapyPath));
76+
deleteSection(`${therapyPath}/name`, snapshot.val(), treatmentReview, treatmentUuid);
77+
}
78+
7479
if (!treatmentUuid || !treatmentName) {
7580
return <></>;
7681
}
@@ -96,7 +101,7 @@ function TherapyCollapsible({
96101
/>
97102
<DeleteSectionButton
98103
sectionName={cancerTypeName}
99-
deleteHandler={() => deleteSection(`${therapyPath}/name`, treatmentReview, treatmentUuid)}
104+
deleteHandler={handleDeleteTherapy}
100105
isRemovableWithoutReview={isRemovableWithoutReview}
101106
/>
102107
</>

src/main/webapp/app/service/firebase/firebase-gene-service.ts

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {
1212
Tumor,
1313
} from 'app/shared/model/firebase/firebase.model';
1414
import { isTxLevelPresent } from 'app/shared/util/firebase/firebase-level-utils';
15-
import { parseFirebaseGenePath } from 'app/shared/util/firebase/firebase-path-utils';
15+
import { extractArrayPath, parseFirebaseGenePath } from 'app/shared/util/firebase/firebase-path-utils';
1616
import { FirebaseGeneReviewService } from 'app/service/firebase/firebase-gene-review-service';
17-
import { getFirebaseGenePath, isSectionRemovableWithoutReview } from 'app/shared/util/firebase/firebase-utils';
17+
import { findNestedUuids, getFirebaseGenePath, isSectionRemovableWithoutReview } from 'app/shared/util/firebase/firebase-utils';
1818
import AuthStore from '../../stores/authentication.store';
1919
import { FirebaseRepository } from '../../stores/firebase/firebase-repository';
2020
import { FirebaseMetaService } from './firebase-meta-service';
@@ -24,6 +24,7 @@ import { notifyError } from 'app/oncokb-commons/components/util/NotificationUtil
2424
import { getErrorMessage } from 'app/oncokb-commons/components/alert/ErrorAlertUtils';
2525
import { FirebaseDataStore } from 'app/stores/firebase/firebase-data.store';
2626
import { getUpdatedReview } from 'app/shared/util/firebase/firebase-review-utils';
27+
import { SentryError } from 'app/config/sentry-error';
2728

2829
export type AllLevelSummary = {
2930
[mutationUuid: string]: {
@@ -192,7 +193,13 @@ export class FirebaseGeneService {
192193
return summary;
193194
};
194195

195-
deleteSection = async (path: string, review: Review, uuid: string, isDemotedToVus = false) => {
196+
deleteSection = async (
197+
path: string,
198+
sectionObject: Mutation | Tumor | Treatment | GenomicIndicator,
199+
review: Review,
200+
uuid: string,
201+
isDemotedToVus = false,
202+
) => {
196203
const isGermline = path.toLowerCase().includes('germline');
197204
const name = this.authStore.fullName;
198205
const pathDetails = parseFirebaseGenePath(path);
@@ -211,21 +218,25 @@ export class FirebaseGeneService {
211218
}
212219

213220
if (removeWithoutReview) {
214-
const pathParts = path.split('/');
215-
const indexToRemove = parseInt(pathParts.pop(), 10);
216-
const arrayPath = pathParts.join('/');
217-
return this.firebaseRepository.deleteFromArray(arrayPath, [indexToRemove]);
221+
const { firebaseArrayPath, deleteIndex } = extractArrayPath(path);
222+
const nestedUuids = findNestedUuids(sectionObject);
223+
try {
224+
await this.firebaseRepository.deleteFromArray(firebaseArrayPath, [deleteIndex]);
225+
for (const id of nestedUuids) {
226+
await this.firebaseMetaService.updateGeneReviewUuid(hugoSymbol, id, false, isGermline);
227+
}
228+
} catch (error) {
229+
throw new SentryError('Failed to delete without review', { path, sectionObject, review, uuid, isDemotedToVus });
230+
}
231+
} else {
232+
// Let the deletion be reviewed
233+
try {
234+
await this.firebaseRepository.update(getFirebaseGenePath(isGermline, hugoSymbol), { [`${pathFromGene}_review`]: review });
235+
await this.firebaseMetaService.updateMeta(hugoSymbol, uuid, true, isGermline);
236+
} catch (error) {
237+
throw new SentryError('Failed to mark deletion for review', { path, sectionObject, review, uuid, isDemotedToVus });
238+
}
218239
}
219-
220-
// Let the deletion be reviewed
221-
return this.firebaseRepository
222-
.update(getFirebaseGenePath(isGermline, hugoSymbol), {
223-
[`${pathFromGene}_review`]: review,
224-
})
225-
.then(() => {
226-
this.firebaseMetaService.updateGeneMetaContent(hugoSymbol, isGermline);
227-
this.firebaseMetaService.updateGeneReviewUuid(hugoSymbol, uuid, true, isGermline);
228-
});
229240
};
230241

231242
createGene = async (hugoSymbol: string, isGermline: boolean, routeAfter?: string) => {

src/main/webapp/app/shared/table/GenomicIndicatorsTable.tsx

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ import './genomic-indicators-table.scss';
1313
import { DeleteSectionButton } from 'app/pages/curation/button/DeleteSectionButton';
1414
import DefaultBadge from '../badge/DefaultBadge';
1515
import { DELETED_SECTION_TOOLTIP_OVERLAY } from 'app/pages/curation/BadgeGroup';
16-
import { getFirebasePath, getMutationName, isSectionRemovableWithoutReview } from '../util/firebase/firebase-utils';
16+
import { findNestedUuids, getFirebasePath, getMutationName, isSectionRemovableWithoutReview } from '../util/firebase/firebase-utils';
1717
import { DANGER } from 'app/config/colors';
1818
import { getHexColorWithAlpha } from '../util/utils';
1919
import { parseFirebaseGenePath } from '../util/firebase/firebase-path-utils';
2020
import GenomicIndicatorsHeader from 'app/pages/curation/header/GenomicIndicatorsHeader';
21+
import { SentryError } from 'app/config/sentry-error';
2122

2223
export interface IGenomicIndicatorsTableProps extends StoreProps {
2324
genomicIndicatorsPath: string;
@@ -32,6 +33,7 @@ const GenomicIndicatorsTable = ({
3233
updateReviewableContent,
3334
updateGeneMetaContent,
3435
updateGeneReviewUuid,
36+
updateMeta,
3537
fetchGenomicIndicators,
3638
}: IGenomicIndicatorsTableProps) => {
3739
const [genomicIndicatorsLength, setGenomicIndicatorsLength] = useState<number>(0);
@@ -48,17 +50,27 @@ const GenomicIndicatorsTable = ({
4850
const review = new Review(name, undefined, undefined, true);
4951

5052
if (removeWithoutReview) {
51-
await deleteGenomicIndicators(genomicIndicatorsPath, [index]);
52-
return await fetchGenomicIndicators(genomicIndicatorsPath);
53+
const nestedUuids = findNestedUuids(genomicIndicator);
54+
try {
55+
await deleteGenomicIndicators(genomicIndicatorsPath, [index]);
56+
for (const id of nestedUuids) {
57+
await updateGeneReviewUuid(hugoSymbol, id, false, true);
58+
}
59+
return await fetchGenomicIndicators(genomicIndicatorsPath);
60+
} catch (error) {
61+
throw new SentryError('Failed to genomic indicator without review', { genomicIndicator, index });
62+
}
5363
}
5464

5565
// Let the deletion be reviewed
56-
return update(ref(firebaseDb, `${getFirebasePath('GERMLINE_GENE', hugoSymbol)}`), {
57-
[`${pathFromGene}_review`]: review,
58-
}).then(() => {
59-
updateGeneMetaContent(hugoSymbol, true);
60-
updateGeneReviewUuid(hugoSymbol, genomicIndicator.name_uuid, true, true);
61-
});
66+
try {
67+
await update(ref(firebaseDb, `${getFirebasePath('GERMLINE_GENE', hugoSymbol)}`), {
68+
[`${pathFromGene}_review`]: review,
69+
});
70+
await updateMeta(hugoSymbol, genomicIndicator.name_uuid, true, true);
71+
} catch (error) {
72+
throw new SentryError('Failed to mark genomic indicator deletion for review', { genomicIndicator, index });
73+
}
6274
}
6375

6476
useEffect(() => {
@@ -308,6 +320,7 @@ const mapStoreToProps = ({
308320
updateGeneReviewUuid: firebaseMetaService.updateGeneReviewUuid,
309321
mutations: firebaseMutationListStore.data,
310322
fetchGenomicIndicators: firebaseGenomicIndicatorsStore.fetchData,
323+
updateMeta: firebaseMetaService.updateMeta,
311324
});
312325

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

src/main/webapp/app/shared/util/firebase/firebase-review-utils.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,3 +823,16 @@ export const getUpdatedReview = (oldReview: Review, currentValue: any, newValue:
823823

824824
return { updatedReview: oldReview, isChangeReverted };
825825
};
826+
827+
export const hasReview = (review: Review) => {
828+
if (review.lastReviewed === '' || !_.isEmpty(review.lastReviewed)) {
829+
return true;
830+
}
831+
const reviewableKeys: (keyof Review)[] = ['added', 'promotedToMutation', 'removed', 'demotedToVus', 'initialUpdate'];
832+
for (const key of reviewableKeys) {
833+
if (review[key] === true) {
834+
return true;
835+
}
836+
}
837+
return false;
838+
};

src/main/webapp/app/shared/util/firebase/firebase-utils.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { replaceUrlParams } from '../url-utils';
2727
import { extractPositionFromSingleNucleotideAlteration, isUuid, parseAlterationName } from '../utils';
2828
import { isTxLevelPresent } from './firebase-level-utils';
2929
import { parseFirebaseGenePath } from './firebase-path-utils';
30+
import { hasReview } from './firebase-review-utils';
3031

3132
export const getValueByNestedKey = (obj: any, nestedKey = '') => {
3233
return nestedKey.split('/').reduce((currObj, currKey) => {
@@ -784,3 +785,27 @@ export const getReviewInfo = (editor: string, action: string, updateTime?: strin
784785
export const getAllCommentsString = (comments: Comment[]) => {
785786
return comments.map(comment => comment.content).join('\n');
786787
};
788+
789+
export function findNestedUuids(obj: any, uuids: string[] = []) {
790+
// Base case: if the input is not an object, return
791+
if (typeof obj !== 'object' || obj === null) {
792+
return uuids;
793+
}
794+
795+
// Iterate through each key in the object
796+
for (const key of Object.keys(obj)) {
797+
if (key.endsWith('_review') && hasReview(obj[key])) {
798+
// If the key ends with "_review" and has reviewable content, add the corresponding "_uuid" key to the result array
799+
const uuidKey = key.slice(0, -7) + '_uuid';
800+
if (obj[uuidKey]) {
801+
uuids.push(obj[uuidKey]);
802+
}
803+
}
804+
// If the value is an object, recursively call the function
805+
if (typeof obj[key] === 'object') {
806+
findNestedUuids(obj[key], uuids);
807+
}
808+
}
809+
810+
return uuids;
811+
}

0 commit comments

Comments
 (0)