From 65258e21c269807649e7db3fbd758976116e61ad Mon Sep 17 00:00:00 2001 From: Bill Nguyen <158210220+bnguyen-bcgsc@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:41:31 -0700 Subject: [PATCH 1/4] DEVSU-2233 Add Option to Move Somatic Variant to Key Alterations Section - DEVSU-2233 - Add options to move SV, CNV, SNV, or EXP Outlier variants to the Key Alterations section in the Summary --- app/common.d.ts | 8 +- .../components/EditDialog/index.scss | 6 + .../components/EditDialog/index.tsx | 117 ++++++++++++++++++ .../components/CopyNumber/index.tsx | 25 ++++ .../components/EditDialog/index.scss | 6 + .../components/EditDialog/index.tsx | 117 ++++++++++++++++++ .../components/Expression/index.tsx | 28 +++++ .../components/MutationSignatures/index.tsx | 6 +- .../components/EditDialog/index.scss | 6 + .../components/EditDialog/index.tsx | 117 ++++++++++++++++++ .../components/SmallMutations/index.tsx | 41 +++++- .../components/EditDialog/index.scss | 6 + .../components/EditDialog/index.tsx | 117 ++++++++++++++++++ .../components/StructuralVariants/index.tsx | 26 ++++ 14 files changed, 616 insertions(+), 10 deletions(-) create mode 100644 app/views/ReportView/components/CopyNumber/components/EditDialog/index.scss create mode 100644 app/views/ReportView/components/CopyNumber/components/EditDialog/index.tsx create mode 100644 app/views/ReportView/components/Expression/components/EditDialog/index.scss create mode 100644 app/views/ReportView/components/Expression/components/EditDialog/index.tsx create mode 100644 app/views/ReportView/components/SmallMutations/components/EditDialog/index.scss create mode 100644 app/views/ReportView/components/SmallMutations/components/EditDialog/index.tsx create mode 100644 app/views/ReportView/components/StructuralVariants/components/EditDialog/index.scss create mode 100644 app/views/ReportView/components/StructuralVariants/components/EditDialog/index.tsx diff --git a/app/common.d.ts b/app/common.d.ts index 5aa867af0..e9c12adaa 100644 --- a/app/common.d.ts +++ b/app/common.d.ts @@ -156,6 +156,7 @@ type CopyNumberType = { kbMatches?: KbMatchType<'cnv'>[]; log2Cna: string | null; lohState: string | null; + selected: boolean; size: number | null; start: number | null; variantType: 'cnv'; @@ -182,6 +183,7 @@ type StructuralVariantType = { ntermGene: string | null; ntermTranscript: string | null; omicSupport: boolean; + selected: boolean; svg: string | null; svgTitle: string | null; variantType: 'sv'; @@ -209,6 +211,7 @@ type SmallMutationType = { rnaAltCount: number | null; rnaDepth: number | null; rnaRefCount: number | null; + selected: boolean; startPosition: number | null; transcript: string | null; tumourAltCopies: number | null; @@ -242,11 +245,14 @@ type ExpOutliersType = { primarySitekIQR: number | null; rnaReads: number | null; rpkm: number | null; + selected: boolean; tpm: number | null; variantType: 'exp'; } & RecordDefaults; type TmburType = { + adjustedTmb: number | null; + adjustedTmbComment: string | null; cdsBasesIn1To22AndXAndY: string; cdsIndels: number; cdsIndelTmb: number; @@ -255,8 +261,6 @@ type TmburType = { comments: string; genomeSnvTmb: number; genomeIndelTmb: number; - adjustedTmb: number | null; - adjustedTmbComment: string | null; tmbHidden: boolean; kbCategory: string | null; kbMatches: KbMatchType[]; diff --git a/app/views/ReportView/components/CopyNumber/components/EditDialog/index.scss b/app/views/ReportView/components/CopyNumber/components/EditDialog/index.scss new file mode 100644 index 000000000..47d0d46cd --- /dev/null +++ b/app/views/ReportView/components/CopyNumber/components/EditDialog/index.scss @@ -0,0 +1,6 @@ +.dialog { + &__form-control { + min-width: 160px; + margin: 0 0 16px; + } +} \ No newline at end of file diff --git a/app/views/ReportView/components/CopyNumber/components/EditDialog/index.tsx b/app/views/ReportView/components/CopyNumber/components/EditDialog/index.tsx new file mode 100644 index 000000000..2ff945414 --- /dev/null +++ b/app/views/ReportView/components/CopyNumber/components/EditDialog/index.tsx @@ -0,0 +1,117 @@ +import React, { + useState, useEffect, useContext, useCallback, +} from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, +} from '@mui/material'; + +import api from '@/services/api'; +import AsyncButton from '@/components/AsyncButton'; + +import ConfirmContext from '@/context/ConfirmContext'; +import ReportContext from '@/context/ReportContext'; +import snackbar from '@/services/SnackbarUtils'; +import useConfirmDialog from '@/hooks/useConfirmDialog'; +import { CopyNumberType } from '@/common'; +import { GeneVariantType } from '../../../GenomicSummary/types'; + +import './index.scss'; + +type EditDialogProps = { + editData: CopyNumberType; + isOpen: boolean; + onClose: (newData?: CopyNumberType) => void; + showErrorSnackbar: (message: string) => void; +}; + +const EditDialog = ({ + editData, + isOpen = false, + onClose, + showErrorSnackbar, +}: EditDialogProps): JSX.Element => { + const { showConfirmDialog } = useConfirmDialog(); + const { report } = useContext(ReportContext); + const { isSigned } = useContext(ConfirmContext); + const [variant, setVariant] = useState(''); + const [variants, setVariants] = useState(); + const [isApiCalling, setIsApiCalling] = useState(false); + + useEffect(() => { + if (editData) { + const newVariant = `${editData?.gene.name} (${editData?.cnvState})`; + setVariant(newVariant); + } + }, [editData]); + + useEffect(() => { + async function fetchVariants() { + const variantsResp = api.get(`/reports/${report.ident}/summary/genomic-alterations-identified`).request(); + if (variantsResp) { + setVariants(await variantsResp); + } + } + + fetchVariants(); + }, [report.ident]); + + const availableVariants = variants?.map(({ geneVariant }) => geneVariant); + + const handleSubmit = useCallback(async () => { + if (!availableVariants.includes(variant)) { + setIsApiCalling(true); + const req = api.post(`/reports/${report.ident}/summary/genomic-alterations-identified`, { geneVariant: variant }); + try { + if (isSigned) { + showConfirmDialog(req); + setIsApiCalling(false); + } else { + await req.request(); + onClose({ ...editData }); + } + snackbar.success('Variant added to key alterations.'); + } catch (err) { + showErrorSnackbar(`Error updating key alterations: ${err.message}`); + onClose(); + } finally { + setIsApiCalling(false); + } + } else { + snackbar.error('Variant already in key alterations.'); + onClose(); + } + }, [availableVariants, variant, report.ident, isSigned, showConfirmDialog, onClose, editData, showErrorSnackbar]); + + const handleClose = useCallback(() => { + onClose(); + }, [onClose]); + + return ( + + Key Alterations Edit + +
+

+ Variant: + {' '} + {variant} +

+
+ + + Add to Summary + + + +
+
+ ); +}; + +export default EditDialog; diff --git a/app/views/ReportView/components/CopyNumber/index.tsx b/app/views/ReportView/components/CopyNumber/index.tsx index fcd91513b..cdf23c507 100644 --- a/app/views/ReportView/components/CopyNumber/index.tsx +++ b/app/views/ReportView/components/CopyNumber/index.tsx @@ -8,6 +8,7 @@ import { import DataTable from '@/components/DataTable'; import ReportContext from '@/context/ReportContext'; +import useReport from '@/hooks/useReport'; import api, { ApiCallSet } from '@/services/api'; import { CNVSTATE, EXPLEVEL } from '@/constants'; import Image from '@/components/Image'; @@ -15,6 +16,7 @@ import ImageType from '@/components/Image/types'; import snackbar from '@/services/SnackbarUtils'; import { CopyNumberType } from '@/common'; import withLoading, { WithLoadingInjectedProps } from '@/hoc/WithLoading'; +import EditDialog from './components/EditDialog'; import columnDefs from './columnDefs'; import './index.scss'; @@ -50,6 +52,7 @@ const CopyNumber = ({ setIsLoading, }: CopyNumberProps): JSX.Element => { const { report } = useContext(ReportContext); + const { canEdit } = useReport(); const theme = useTheme(); const [images, setImages] = useState([]); const [circos, setCircos] = useState(); @@ -70,6 +73,8 @@ const CopyNumber = ({ } return accumulator; }, []), ); + const [showDialog, setShowDialog] = useState(false); + const [editData, setEditData] = useState(); useEffect(() => { if (report) { @@ -187,6 +192,16 @@ const CopyNumber = ({ maxHeight: `calc(100vh - ${theme.mixins.toolbar.minHeight as number * 3}px)`, }), [theme.mixins?.toolbar?.minHeight]); + const handleEditStart = (rowData: CopyNumberType) => { + setShowDialog(true); + setEditData(rowData); + }; + + const handleEditClose = () => { + setShowDialog(false); + setEditData(null); + }; + const handleVisibleColsChange = (change) => setVisibleCols(change); return ( @@ -195,6 +210,14 @@ const CopyNumber = ({ {!isLoading && ( <> Summary of Copy Number Events + {showDialog && ( + + )} {circos ? (
))} diff --git a/app/views/ReportView/components/Expression/components/EditDialog/index.scss b/app/views/ReportView/components/Expression/components/EditDialog/index.scss new file mode 100644 index 000000000..47d0d46cd --- /dev/null +++ b/app/views/ReportView/components/Expression/components/EditDialog/index.scss @@ -0,0 +1,6 @@ +.dialog { + &__form-control { + min-width: 160px; + margin: 0 0 16px; + } +} \ No newline at end of file diff --git a/app/views/ReportView/components/Expression/components/EditDialog/index.tsx b/app/views/ReportView/components/Expression/components/EditDialog/index.tsx new file mode 100644 index 000000000..e15e84a80 --- /dev/null +++ b/app/views/ReportView/components/Expression/components/EditDialog/index.tsx @@ -0,0 +1,117 @@ +import React, { + useState, useEffect, useContext, useCallback, +} from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, +} from '@mui/material'; + +import api from '@/services/api'; +import AsyncButton from '@/components/AsyncButton'; + +import ConfirmContext from '@/context/ConfirmContext'; +import ReportContext from '@/context/ReportContext'; +import snackbar from '@/services/SnackbarUtils'; +import useConfirmDialog from '@/hooks/useConfirmDialog'; +import { ExpOutliersType } from '@/common'; +import { GeneVariantType } from '../../../GenomicSummary/types'; + +import './index.scss'; + +type EditDialogProps = { + editData: ExpOutliersType; + isOpen: boolean; + onClose: (newData?: ExpOutliersType) => void; + showErrorSnackbar: (message: string) => void; +}; + +const EditDialog = ({ + editData, + isOpen = false, + onClose, + showErrorSnackbar, +}: EditDialogProps): JSX.Element => { + const { showConfirmDialog } = useConfirmDialog(); + const { report } = useContext(ReportContext); + const { isSigned } = useContext(ConfirmContext); + const [variant, setVariant] = useState(''); + const [variants, setVariants] = useState(); + const [isApiCalling, setIsApiCalling] = useState(false); + + useEffect(() => { + if (editData) { + const newVariant = `${editData?.gene.name} (${editData?.expressionState})`; + setVariant(newVariant); + } + }, [editData]); + + useEffect(() => { + async function fetchVariants() { + const variantsResp = api.get(`/reports/${report.ident}/summary/genomic-alterations-identified`).request(); + if (variantsResp) { + setVariants(await variantsResp); + } + } + + fetchVariants(); + }, [report.ident]); + + const availableVariants = variants?.map(({ geneVariant }) => geneVariant); + + const handleSubmit = useCallback(async () => { + if (!availableVariants.includes(variant)) { + setIsApiCalling(true); + const req = api.post(`/reports/${report.ident}/summary/genomic-alterations-identified`, { geneVariant: variant }); + try { + if (isSigned) { + showConfirmDialog(req); + setIsApiCalling(false); + } else { + await req.request(); + onClose({ ...editData }); + } + snackbar.success('Variant added to key alterations.'); + } catch (err) { + showErrorSnackbar(`Error updating key alterations: ${err.message}`); + onClose(); + } finally { + setIsApiCalling(false); + } + } else { + snackbar.error('Variant already in key alterations.'); + onClose(); + } + }, [availableVariants, variant, report.ident, isSigned, showConfirmDialog, onClose, editData, showErrorSnackbar]); + + const handleClose = useCallback(() => { + onClose(); + }, [onClose]); + + return ( + + Key Alterations Edit + +
+

+ Small Mutation: + {' '} + {variant} +

+
+ + + Add to Summary + + + +
+
+ ); +}; + +export default EditDialog; diff --git a/app/views/ReportView/components/Expression/index.tsx b/app/views/ReportView/components/Expression/index.tsx index 32e296a14..f316ffba8 100644 --- a/app/views/ReportView/components/Expression/index.tsx +++ b/app/views/ReportView/components/Expression/index.tsx @@ -4,10 +4,14 @@ import React, { import { Typography, Paper } from '@mui/material'; import DataTable from '@/components/DataTable'; import api from '@/services/api'; +import snackbar from '@/services/SnackbarUtils'; import DemoDescription from '@/components/DemoDescription'; import ReportContext from '@/context/ReportContext'; +import useReport from '@/hooks/useReport'; import withLoading, { WithLoadingInjectedProps } from '@/hoc/WithLoading'; import { ImageType } from '@/components/Image'; +import { ExpOutliersType } from '@/common'; +import EditDialog from './components/EditDialog'; import columnDefs from './columnDefs'; import processExpression from './processData'; import { @@ -59,6 +63,7 @@ const Expression = ({ setIsLoading, }: ExpressionProps): JSX.Element => { const { report } = useContext(ReportContext); + const { canEdit } = useReport(); const [tissueSites, setTissueSites] = useState(); const [comparators, setComparators] = useState(); const [expOutliers, setExpOutliers] = useState(); @@ -66,6 +71,9 @@ const Expression = ({ columnDefs.reduce(getVisibleColsFromColDefReducer, []), ); + const [showDialog, setShowDialog] = useState(false); + const [editData, setEditData] = useState(); + useEffect(() => { if (report && report.ident) { const getData = async () => { @@ -159,6 +167,16 @@ const Expression = ({ } }, [report]); + const handleEditStart = (rowData: ExpOutliersType) => { + setShowDialog(true); + setEditData(rowData); + }; + + const handleEditClose = () => { + setShowDialog(false); + setEditData(null); + }; + return (!isLoading && ( <>
@@ -218,6 +236,14 @@ const Expression = ({ No comparator data to display )}
+ {showDialog && ( + + )} {expOutliers && (Object.entries(TITLE_MAP).map(([key, titleText]) => ( )))} diff --git a/app/views/ReportView/components/MutationSignatures/index.tsx b/app/views/ReportView/components/MutationSignatures/index.tsx index 1f9954bf7..31f8e73d0 100644 --- a/app/views/ReportView/components/MutationSignatures/index.tsx +++ b/app/views/ReportView/components/MutationSignatures/index.tsx @@ -49,9 +49,9 @@ const MutationSignatures = ({ api.get(`/reports/${report.ident}/mutation-signatures`).request(), ]); setImages(imageData); - setSbsSignatures(signatureData.filter((sig) => !(new RegExp(/dbs|id/)).test(sig.signature.toLowerCase()))); - setDbsSignatures(signatureData.filter((sig) => (new RegExp(/dbs/)).test(sig.signature.toLowerCase()))); - setIdSignatures(signatureData.filter((sig) => (new RegExp(/id/)).test(sig.signature.toLowerCase()))); + setSbsSignatures(signatureData.filter((sig) => !(/dbs|id/).test(sig.signature.toLowerCase()))); + setDbsSignatures(signatureData.filter((sig) => (/dbs/).test(sig.signature.toLowerCase()))); + setIdSignatures(signatureData.filter((sig) => (/id/).test(sig.signature.toLowerCase()))); } catch (err) { snackbar.error(`Network error: ${err}`); } finally { diff --git a/app/views/ReportView/components/SmallMutations/components/EditDialog/index.scss b/app/views/ReportView/components/SmallMutations/components/EditDialog/index.scss new file mode 100644 index 000000000..47d0d46cd --- /dev/null +++ b/app/views/ReportView/components/SmallMutations/components/EditDialog/index.scss @@ -0,0 +1,6 @@ +.dialog { + &__form-control { + min-width: 160px; + margin: 0 0 16px; + } +} \ No newline at end of file diff --git a/app/views/ReportView/components/SmallMutations/components/EditDialog/index.tsx b/app/views/ReportView/components/SmallMutations/components/EditDialog/index.tsx new file mode 100644 index 000000000..6359718a4 --- /dev/null +++ b/app/views/ReportView/components/SmallMutations/components/EditDialog/index.tsx @@ -0,0 +1,117 @@ +import React, { + useState, useEffect, useContext, useCallback, +} from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, +} from '@mui/material'; + +import api from '@/services/api'; +import AsyncButton from '@/components/AsyncButton'; + +import ConfirmContext from '@/context/ConfirmContext'; +import ReportContext from '@/context/ReportContext'; +import snackbar from '@/services/SnackbarUtils'; +import useConfirmDialog from '@/hooks/useConfirmDialog'; +import { SmallMutationType } from '@/common'; +import { GeneVariantType } from '../../../GenomicSummary/types'; + +import './index.scss'; + +type EditDialogProps = { + editData: SmallMutationType; + isOpen: boolean; + onClose: (newData?: SmallMutationType) => void; + showErrorSnackbar: (message: string) => void; +}; + +const EditDialog = ({ + editData, + isOpen = false, + onClose, + showErrorSnackbar, +}: EditDialogProps): JSX.Element => { + const { showConfirmDialog } = useConfirmDialog(); + const { report } = useContext(ReportContext); + const { isSigned } = useContext(ConfirmContext); + const [variant, setVariant] = useState(''); + const [variants, setVariants] = useState(); + const [isApiCalling, setIsApiCalling] = useState(false); + + useEffect(() => { + if (editData) { + const newVariant = `${editData?.gene.name}:${editData?.proteinChange}`; + setVariant(newVariant); + } + }, [editData]); + + useEffect(() => { + async function fetchVariants() { + const variantsResp = api.get(`/reports/${report.ident}/summary/genomic-alterations-identified`).request(); + if (variantsResp) { + setVariants(await variantsResp); + } + } + + fetchVariants(); + }, [report.ident]); + + const availableVariants = variants?.map(({ geneVariant }) => geneVariant); + + const handleSubmit = useCallback(async () => { + if (!availableVariants.includes(variant)) { + setIsApiCalling(true); + const req = api.post(`/reports/${report.ident}/summary/genomic-alterations-identified`, { geneVariant: variant }); + try { + if (isSigned) { + showConfirmDialog(req); + setIsApiCalling(false); + } else { + await req.request(); + onClose({ ...editData }); + } + snackbar.success('Variant added to key alterations.'); + } catch (err) { + showErrorSnackbar(`Error updating key alterations: ${err.message}`); + onClose(); + } finally { + setIsApiCalling(false); + } + } else { + snackbar.error('Variant already in key alterations.'); + onClose(); + } + }, [availableVariants, variant, report.ident, isSigned, showConfirmDialog, onClose, editData, showErrorSnackbar]); + + const handleClose = useCallback(() => { + onClose(); + }, [onClose]); + + return ( + + Key Alterations Edit + +
+

+ Small Mutation: + {' '} + {variant} +

+
+ + + Add to Summary + + + +
+
+ ); +}; + +export default EditDialog; diff --git a/app/views/ReportView/components/SmallMutations/index.tsx b/app/views/ReportView/components/SmallMutations/index.tsx index c6dc76554..2bb6c8af3 100644 --- a/app/views/ReportView/components/SmallMutations/index.tsx +++ b/app/views/ReportView/components/SmallMutations/index.tsx @@ -7,8 +7,10 @@ import api from '@/services/api'; import snackbar from '@/services/SnackbarUtils'; import DataTable from '@/components/DataTable'; import ReportContext from '@/context/ReportContext'; +import useReport from '@/hooks/useReport'; import { SmallMutationType } from '@/common'; import withLoading, { WithLoadingInjectedProps } from '@/hoc/WithLoading'; +import EditDialog from './components/EditDialog'; import { columnDefs } from './columnDefs'; import './index.scss'; @@ -36,6 +38,7 @@ const SmallMutations = ({ setIsLoading, }: SmallMutationsProps): JSX.Element => { const { report } = useContext(ReportContext); + const { canEdit } = useReport(); const [smallMutations, setSmallMutations] = useState([]); const [groupedSmallMutations, setGroupedSmallMutations] = useState({ therapeutic: [], @@ -51,6 +54,9 @@ const SmallMutations = ({ }, []), ); + const [showDialog, setShowDialog] = useState(false); + const [editData, setEditData] = useState(); + useEffect(() => { if (report) { const getData = async () => { @@ -68,6 +74,7 @@ const SmallMutations = ({ for (const { gene: { expressionVariants: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars tpm, rpkm, primarySiteFoldChange, }, }, @@ -132,6 +139,16 @@ const SmallMutations = ({ } }, [smallMutations]); + const handleEditStart = (rowData: SmallMutationType) => { + setShowDialog(true); + setEditData(rowData); + }; + + const handleEditClose = () => { + setShowDialog(false); + setEditData(null); + }; + const handleVisibleColsChange = (change) => setVisibleCols(change); const dataTables = useMemo(() => Object.entries(groupedSmallMutations).map(([key, value]) => ( @@ -144,15 +161,29 @@ const SmallMutations = ({ syncVisibleColumns={handleVisibleColsChange} visibleColumns={visibleCols} demoDescription={INFO_BUBBLES[key]} + canEdit={canEdit} + onEdit={handleEditStart} /> - )), [groupedSmallMutations, visibleCols]); + )), [canEdit, groupedSmallMutations, visibleCols]); return (
- - Small Mutations - - { !isLoading ? dataTables : null } + {!isLoading && ( + <> + + Small Mutations + + {showDialog && ( + + )} + { dataTables } + + )}
); }; diff --git a/app/views/ReportView/components/StructuralVariants/components/EditDialog/index.scss b/app/views/ReportView/components/StructuralVariants/components/EditDialog/index.scss new file mode 100644 index 000000000..47d0d46cd --- /dev/null +++ b/app/views/ReportView/components/StructuralVariants/components/EditDialog/index.scss @@ -0,0 +1,6 @@ +.dialog { + &__form-control { + min-width: 160px; + margin: 0 0 16px; + } +} \ No newline at end of file diff --git a/app/views/ReportView/components/StructuralVariants/components/EditDialog/index.tsx b/app/views/ReportView/components/StructuralVariants/components/EditDialog/index.tsx new file mode 100644 index 000000000..5c33ccfb2 --- /dev/null +++ b/app/views/ReportView/components/StructuralVariants/components/EditDialog/index.tsx @@ -0,0 +1,117 @@ +import React, { + useState, useEffect, useContext, useCallback, +} from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, +} from '@mui/material'; + +import api from '@/services/api'; +import AsyncButton from '@/components/AsyncButton'; + +import ConfirmContext from '@/context/ConfirmContext'; +import ReportContext from '@/context/ReportContext'; +import snackbar from '@/services/SnackbarUtils'; +import useConfirmDialog from '@/hooks/useConfirmDialog'; +import { StructuralVariantType } from '@/common'; +import { GeneVariantType } from '../../../GenomicSummary/types'; + +import './index.scss'; + +type EditDialogProps = { + editData: StructuralVariantType; + isOpen: boolean; + onClose: (newData?: StructuralVariantType) => void; + showErrorSnackbar: (message: string) => void; +}; + +const EditDialog = ({ + editData, + isOpen = false, + onClose, + showErrorSnackbar, +}: EditDialogProps): JSX.Element => { + const { showConfirmDialog } = useConfirmDialog(); + const { report } = useContext(ReportContext); + const { isSigned } = useContext(ConfirmContext); + const [variant, setVariant] = useState(''); + const [variants, setVariants] = useState(); + const [isApiCalling, setIsApiCalling] = useState(false); + + useEffect(() => { + if (editData) { + const newVariant = `${editData?.displayName}`; + setVariant(newVariant); + } + }, [editData]); + + useEffect(() => { + async function fetchVariants() { + const variantsResp = api.get(`/reports/${report.ident}/summary/genomic-alterations-identified`).request(); + if (variantsResp) { + setVariants(await variantsResp); + } + } + + fetchVariants(); + }, [report.ident]); + + const availableVariants = variants?.map(({ geneVariant }) => geneVariant); + + const handleSubmit = useCallback(async () => { + if (!availableVariants.includes(variant)) { + setIsApiCalling(true); + const req = api.post(`/reports/${report.ident}/summary/genomic-alterations-identified`, { geneVariant: variant }); + try { + if (isSigned) { + showConfirmDialog(req); + setIsApiCalling(false); + } else { + await req.request(); + onClose({ ...editData }); + } + snackbar.success('Variant added to key alterations.'); + } catch (err) { + showErrorSnackbar(`Error updating key alterations: ${err.message}`); + onClose(); + } finally { + setIsApiCalling(false); + } + } else { + snackbar.error('Variant already in key alterations.'); + onClose(); + } + }, [availableVariants, variant, report.ident, isSigned, showConfirmDialog, onClose, editData, showErrorSnackbar]); + + const handleClose = useCallback(() => { + onClose(); + }, [onClose]); + + return ( + + Key Alterations Edit + +
+

+ Small Mutation: + {' '} + {variant} +

+
+ + + Add to Summary + + + +
+
+ ); +}; + +export default EditDialog; diff --git a/app/views/ReportView/components/StructuralVariants/index.tsx b/app/views/ReportView/components/StructuralVariants/index.tsx index 09eea52db..a2e1dde9f 100644 --- a/app/views/ReportView/components/StructuralVariants/index.tsx +++ b/app/views/ReportView/components/StructuralVariants/index.tsx @@ -13,9 +13,11 @@ import snackbar from '@/services/SnackbarUtils'; import DataTable from '@/components/DataTable'; import Image from '@/components/Image'; import ReportContext from '@/context/ReportContext'; +import useReport from '@/hooks/useReport'; import ImageType from '@/components/Image/types'; import { StructuralVariantType } from '@/common'; import withLoading, { WithLoadingInjectedProps } from '@/hoc/WithLoading'; +import EditDialog from './components/EditDialog'; import columnDefs from './columnDefs'; import './index.scss'; @@ -43,6 +45,7 @@ const StructuralVariants = ({ setIsLoading, }: StructuralVariantsProps): JSX.Element => { const { report } = useContext(ReportContext); + const { canEdit } = useReport(); const theme = useTheme(); const [svs, setSvs] = useState([]); const [groupedSvs, setGroupedSvs] = useState({ @@ -63,6 +66,9 @@ const StructuralVariants = ({ }, []), ); + const [showDialog, setShowDialog] = useState(false); + const [editData, setEditData] = useState(); + useEffect(() => { if (report) { const getData = async () => { @@ -161,6 +167,16 @@ const StructuralVariants = ({ setTabIndex(newValue); }; + const handleEditStart = (rowData: StructuralVariantType) => { + setShowDialog(true); + setEditData(rowData); + }; + + const handleEditClose = () => { + setShowDialog(false); + setEditData(null); + }; + const handleVisibleColsChange = (change) => setVisibleCols(change); return ( @@ -171,6 +187,14 @@ const StructuralVariants = ({ Summary of Structural Events + {showDialog && ( + + )} {(genomeCircos || transcriptomeCircos) ? ( <> @@ -215,6 +239,8 @@ const StructuralVariants = ({ syncVisibleColumns={handleVisibleColsChange} canToggleColumns demoDescription={INFO_BUBBLES[key]} + canEdit={canEdit} + onEdit={handleEditStart} /> ))} From 235feae2d8a24a85b9dc0cc784a7580836171250 Mon Sep 17 00:00:00 2001 From: bnguyen-bcgsc Date: Mon, 25 Mar 2024 10:35:32 -0700 Subject: [PATCH 2/4] - DEVSU-2233 - Make EditDialog into VariantEditDialog, a global component that all variant report views can use - Refactor VariantEditDialog - Remove redundant EditDialog components for variant views --- app/components/VariantEditDialog/index.scss | 6 + .../VariantEditDialog}/index.tsx | 51 +++++--- .../components/EditDialog/index.scss | 6 - .../components/CopyNumber/index.tsx | 5 +- .../components/EditDialog/index.scss | 6 - .../components/EditDialog/index.tsx | 117 ------------------ .../components/Expression/index.tsx | 5 +- .../components/EditDialog/index.scss | 6 - .../components/EditDialog/index.tsx | 117 ------------------ .../components/SmallMutations/index.tsx | 5 +- .../components/EditDialog/index.scss | 6 - .../components/EditDialog/index.tsx | 117 ------------------ .../components/StructuralVariants/index.tsx | 5 +- 13 files changed, 55 insertions(+), 397 deletions(-) create mode 100644 app/components/VariantEditDialog/index.scss rename app/{views/ReportView/components/CopyNumber/components/EditDialog => components/VariantEditDialog}/index.tsx (68%) delete mode 100644 app/views/ReportView/components/CopyNumber/components/EditDialog/index.scss delete mode 100644 app/views/ReportView/components/Expression/components/EditDialog/index.scss delete mode 100644 app/views/ReportView/components/Expression/components/EditDialog/index.tsx delete mode 100644 app/views/ReportView/components/SmallMutations/components/EditDialog/index.scss delete mode 100644 app/views/ReportView/components/SmallMutations/components/EditDialog/index.tsx delete mode 100644 app/views/ReportView/components/StructuralVariants/components/EditDialog/index.scss delete mode 100644 app/views/ReportView/components/StructuralVariants/components/EditDialog/index.tsx diff --git a/app/components/VariantEditDialog/index.scss b/app/components/VariantEditDialog/index.scss new file mode 100644 index 000000000..11c991e10 --- /dev/null +++ b/app/components/VariantEditDialog/index.scss @@ -0,0 +1,6 @@ +.dialog { + &__form-control { + min-width: 160px; + margin: 0 0 16px; + } + } \ No newline at end of file diff --git a/app/views/ReportView/components/CopyNumber/components/EditDialog/index.tsx b/app/components/VariantEditDialog/index.tsx similarity index 68% rename from app/views/ReportView/components/CopyNumber/components/EditDialog/index.tsx rename to app/components/VariantEditDialog/index.tsx index 2ff945414..f6cecc67e 100644 --- a/app/views/ReportView/components/CopyNumber/components/EditDialog/index.tsx +++ b/app/components/VariantEditDialog/index.tsx @@ -16,24 +16,28 @@ import ConfirmContext from '@/context/ConfirmContext'; import ReportContext from '@/context/ReportContext'; import snackbar from '@/services/SnackbarUtils'; import useConfirmDialog from '@/hooks/useConfirmDialog'; -import { CopyNumberType } from '@/common'; -import { GeneVariantType } from '../../../GenomicSummary/types'; +import { + CopyNumberType, SmallMutationType, StructuralVariantType, ExpOutliersType, +} from '@/common'; +import { GeneVariantType } from '@/views/ReportView/components/GenomicSummary/types'; import './index.scss'; -type EditDialogProps = { - editData: CopyNumberType; - isOpen: boolean; - onClose: (newData?: CopyNumberType) => void; - showErrorSnackbar: (message: string) => void; -}; + type VariantEditDialogProps = { + editData: SmallMutationType | CopyNumberType | StructuralVariantType | ExpOutliersType; + variantType: string; + isOpen: boolean; + onClose: (newData?: SmallMutationType | CopyNumberType | StructuralVariantType | ExpOutliersType) => void; + showErrorSnackbar: (message: string) => void; + }; -const EditDialog = ({ +const VariantEditDialog = ({ editData, + variantType, isOpen = false, onClose, showErrorSnackbar, -}: EditDialogProps): JSX.Element => { +}: VariantEditDialogProps): JSX.Element => { const { showConfirmDialog } = useConfirmDialog(); const { report } = useContext(ReportContext); const { isSigned } = useContext(ConfirmContext); @@ -43,10 +47,29 @@ const EditDialog = ({ useEffect(() => { if (editData) { - const newVariant = `${editData?.gene.name} (${editData?.cnvState})`; - setVariant(newVariant); + let newVariant; + switch (variantType) { + case 'snv': + newVariant = `${editData?.gene.name}:${editData?.proteinChange}`; + setVariant(newVariant); + break; + case 'cnv': + newVariant = `${editData?.gene.name} (${editData?.cnvState})`; + setVariant(newVariant); + break; + case 'sv': + newVariant = `${editData?.displayName}`; + setVariant(newVariant); + break; + case 'exp': + newVariant = `${editData?.gene.name} (${editData?.expressionState})`; + setVariant(newVariant); + break; + default: + break; + } } - }, [editData]); + }, [editData, variantType]); useEffect(() => { async function fetchVariants() { @@ -114,4 +137,4 @@ const EditDialog = ({ ); }; -export default EditDialog; +export default VariantEditDialog; diff --git a/app/views/ReportView/components/CopyNumber/components/EditDialog/index.scss b/app/views/ReportView/components/CopyNumber/components/EditDialog/index.scss deleted file mode 100644 index 47d0d46cd..000000000 --- a/app/views/ReportView/components/CopyNumber/components/EditDialog/index.scss +++ /dev/null @@ -1,6 +0,0 @@ -.dialog { - &__form-control { - min-width: 160px; - margin: 0 0 16px; - } -} \ No newline at end of file diff --git a/app/views/ReportView/components/CopyNumber/index.tsx b/app/views/ReportView/components/CopyNumber/index.tsx index cdf23c507..e92f1614d 100644 --- a/app/views/ReportView/components/CopyNumber/index.tsx +++ b/app/views/ReportView/components/CopyNumber/index.tsx @@ -16,7 +16,7 @@ import ImageType from '@/components/Image/types'; import snackbar from '@/services/SnackbarUtils'; import { CopyNumberType } from '@/common'; import withLoading, { WithLoadingInjectedProps } from '@/hoc/WithLoading'; -import EditDialog from './components/EditDialog'; +import VariantEditDialog from '@/components/VariantEditDialog'; import columnDefs from './columnDefs'; import './index.scss'; @@ -211,8 +211,9 @@ const CopyNumber = ({ <> Summary of Copy Number Events {showDialog && ( - void; - showErrorSnackbar: (message: string) => void; -}; - -const EditDialog = ({ - editData, - isOpen = false, - onClose, - showErrorSnackbar, -}: EditDialogProps): JSX.Element => { - const { showConfirmDialog } = useConfirmDialog(); - const { report } = useContext(ReportContext); - const { isSigned } = useContext(ConfirmContext); - const [variant, setVariant] = useState(''); - const [variants, setVariants] = useState(); - const [isApiCalling, setIsApiCalling] = useState(false); - - useEffect(() => { - if (editData) { - const newVariant = `${editData?.gene.name} (${editData?.expressionState})`; - setVariant(newVariant); - } - }, [editData]); - - useEffect(() => { - async function fetchVariants() { - const variantsResp = api.get(`/reports/${report.ident}/summary/genomic-alterations-identified`).request(); - if (variantsResp) { - setVariants(await variantsResp); - } - } - - fetchVariants(); - }, [report.ident]); - - const availableVariants = variants?.map(({ geneVariant }) => geneVariant); - - const handleSubmit = useCallback(async () => { - if (!availableVariants.includes(variant)) { - setIsApiCalling(true); - const req = api.post(`/reports/${report.ident}/summary/genomic-alterations-identified`, { geneVariant: variant }); - try { - if (isSigned) { - showConfirmDialog(req); - setIsApiCalling(false); - } else { - await req.request(); - onClose({ ...editData }); - } - snackbar.success('Variant added to key alterations.'); - } catch (err) { - showErrorSnackbar(`Error updating key alterations: ${err.message}`); - onClose(); - } finally { - setIsApiCalling(false); - } - } else { - snackbar.error('Variant already in key alterations.'); - onClose(); - } - }, [availableVariants, variant, report.ident, isSigned, showConfirmDialog, onClose, editData, showErrorSnackbar]); - - const handleClose = useCallback(() => { - onClose(); - }, [onClose]); - - return ( - - Key Alterations Edit - -
-

- Small Mutation: - {' '} - {variant} -

-
- - - Add to Summary - - - -
-
- ); -}; - -export default EditDialog; diff --git a/app/views/ReportView/components/Expression/index.tsx b/app/views/ReportView/components/Expression/index.tsx index f316ffba8..b3039d9e0 100644 --- a/app/views/ReportView/components/Expression/index.tsx +++ b/app/views/ReportView/components/Expression/index.tsx @@ -11,7 +11,7 @@ import useReport from '@/hooks/useReport'; import withLoading, { WithLoadingInjectedProps } from '@/hoc/WithLoading'; import { ImageType } from '@/components/Image'; import { ExpOutliersType } from '@/common'; -import EditDialog from './components/EditDialog'; +import VariantEditDialog from '@/components/VariantEditDialog'; import columnDefs from './columnDefs'; import processExpression from './processData'; import { @@ -237,8 +237,9 @@ const Expression = ({ )}
{showDialog && ( - void; - showErrorSnackbar: (message: string) => void; -}; - -const EditDialog = ({ - editData, - isOpen = false, - onClose, - showErrorSnackbar, -}: EditDialogProps): JSX.Element => { - const { showConfirmDialog } = useConfirmDialog(); - const { report } = useContext(ReportContext); - const { isSigned } = useContext(ConfirmContext); - const [variant, setVariant] = useState(''); - const [variants, setVariants] = useState(); - const [isApiCalling, setIsApiCalling] = useState(false); - - useEffect(() => { - if (editData) { - const newVariant = `${editData?.gene.name}:${editData?.proteinChange}`; - setVariant(newVariant); - } - }, [editData]); - - useEffect(() => { - async function fetchVariants() { - const variantsResp = api.get(`/reports/${report.ident}/summary/genomic-alterations-identified`).request(); - if (variantsResp) { - setVariants(await variantsResp); - } - } - - fetchVariants(); - }, [report.ident]); - - const availableVariants = variants?.map(({ geneVariant }) => geneVariant); - - const handleSubmit = useCallback(async () => { - if (!availableVariants.includes(variant)) { - setIsApiCalling(true); - const req = api.post(`/reports/${report.ident}/summary/genomic-alterations-identified`, { geneVariant: variant }); - try { - if (isSigned) { - showConfirmDialog(req); - setIsApiCalling(false); - } else { - await req.request(); - onClose({ ...editData }); - } - snackbar.success('Variant added to key alterations.'); - } catch (err) { - showErrorSnackbar(`Error updating key alterations: ${err.message}`); - onClose(); - } finally { - setIsApiCalling(false); - } - } else { - snackbar.error('Variant already in key alterations.'); - onClose(); - } - }, [availableVariants, variant, report.ident, isSigned, showConfirmDialog, onClose, editData, showErrorSnackbar]); - - const handleClose = useCallback(() => { - onClose(); - }, [onClose]); - - return ( - - Key Alterations Edit - -
-

- Small Mutation: - {' '} - {variant} -

-
- - - Add to Summary - - - -
-
- ); -}; - -export default EditDialog; diff --git a/app/views/ReportView/components/SmallMutations/index.tsx b/app/views/ReportView/components/SmallMutations/index.tsx index 2bb6c8af3..3bd15f5af 100644 --- a/app/views/ReportView/components/SmallMutations/index.tsx +++ b/app/views/ReportView/components/SmallMutations/index.tsx @@ -10,7 +10,7 @@ import ReportContext from '@/context/ReportContext'; import useReport from '@/hooks/useReport'; import { SmallMutationType } from '@/common'; import withLoading, { WithLoadingInjectedProps } from '@/hoc/WithLoading'; -import EditDialog from './components/EditDialog'; +import VariantEditDialog from '@/components/VariantEditDialog'; import { columnDefs } from './columnDefs'; import './index.scss'; @@ -174,8 +174,9 @@ const SmallMutations = ({ Small Mutations {showDialog && ( - void; - showErrorSnackbar: (message: string) => void; -}; - -const EditDialog = ({ - editData, - isOpen = false, - onClose, - showErrorSnackbar, -}: EditDialogProps): JSX.Element => { - const { showConfirmDialog } = useConfirmDialog(); - const { report } = useContext(ReportContext); - const { isSigned } = useContext(ConfirmContext); - const [variant, setVariant] = useState(''); - const [variants, setVariants] = useState(); - const [isApiCalling, setIsApiCalling] = useState(false); - - useEffect(() => { - if (editData) { - const newVariant = `${editData?.displayName}`; - setVariant(newVariant); - } - }, [editData]); - - useEffect(() => { - async function fetchVariants() { - const variantsResp = api.get(`/reports/${report.ident}/summary/genomic-alterations-identified`).request(); - if (variantsResp) { - setVariants(await variantsResp); - } - } - - fetchVariants(); - }, [report.ident]); - - const availableVariants = variants?.map(({ geneVariant }) => geneVariant); - - const handleSubmit = useCallback(async () => { - if (!availableVariants.includes(variant)) { - setIsApiCalling(true); - const req = api.post(`/reports/${report.ident}/summary/genomic-alterations-identified`, { geneVariant: variant }); - try { - if (isSigned) { - showConfirmDialog(req); - setIsApiCalling(false); - } else { - await req.request(); - onClose({ ...editData }); - } - snackbar.success('Variant added to key alterations.'); - } catch (err) { - showErrorSnackbar(`Error updating key alterations: ${err.message}`); - onClose(); - } finally { - setIsApiCalling(false); - } - } else { - snackbar.error('Variant already in key alterations.'); - onClose(); - } - }, [availableVariants, variant, report.ident, isSigned, showConfirmDialog, onClose, editData, showErrorSnackbar]); - - const handleClose = useCallback(() => { - onClose(); - }, [onClose]); - - return ( - - Key Alterations Edit - -
-

- Small Mutation: - {' '} - {variant} -

-
- - - Add to Summary - - - -
-
- ); -}; - -export default EditDialog; diff --git a/app/views/ReportView/components/StructuralVariants/index.tsx b/app/views/ReportView/components/StructuralVariants/index.tsx index a2e1dde9f..82b7bd2e8 100644 --- a/app/views/ReportView/components/StructuralVariants/index.tsx +++ b/app/views/ReportView/components/StructuralVariants/index.tsx @@ -17,7 +17,7 @@ import useReport from '@/hooks/useReport'; import ImageType from '@/components/Image/types'; import { StructuralVariantType } from '@/common'; import withLoading, { WithLoadingInjectedProps } from '@/hoc/WithLoading'; -import EditDialog from './components/EditDialog'; +import VariantEditDialog from '@/components/VariantEditDialog'; import columnDefs from './columnDefs'; import './index.scss'; @@ -188,8 +188,9 @@ const StructuralVariants = ({ Summary of Structural Events {showDialog && ( - Date: Mon, 25 Mar 2024 13:09:59 -0700 Subject: [PATCH 3/4] - DEVSU-2246 - Fix issue where number of reports was not showing in projects - Implement admin rights to Number of Reports and Number of User columns - General linting --- app/views/ProjectsView/columnDefs.ts | 2 +- app/views/ProjectsView/index.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/ProjectsView/columnDefs.ts b/app/views/ProjectsView/columnDefs.ts index 27f545089..6fb48d931 100644 --- a/app/views/ProjectsView/columnDefs.ts +++ b/app/views/ProjectsView/columnDefs.ts @@ -16,7 +16,7 @@ const readOnlyColDefs = [ }, { headerName: 'Number of reports', - valueGetter: ({ data }) => data?.reports?.length, + valueGetter: ({ data }) => Number(data?.reportCount), }, { headerName: 'Number of users', diff --git a/app/views/ProjectsView/index.tsx b/app/views/ProjectsView/index.tsx index eb4de48db..35808f7c7 100644 --- a/app/views/ProjectsView/index.tsx +++ b/app/views/ProjectsView/index.tsx @@ -26,14 +26,14 @@ const Projects = (): JSX.Element => { useEffect(() => { const getData = async () => { - const projectsResp = await api.get('/project?admin=true').request(); + const projectsResp = await api.get(`/project?admin=${adminAccess}`).request(); setProjects(projectsResp); setLoading(false); }; getData(); - }, []); + }, [adminAccess]); const handleEditStart = (rowData) => { setShowDialog(true); @@ -41,7 +41,7 @@ const Projects = (): JSX.Element => { }; const handleDelete = useCallback(async ({ ident }) => { - // eslint-disable-next-line no-restricted-globals + // eslint-disable-next-line no-restricted-globals, no-alert if (confirm('Are you sure you want to remove this project?')) { await api.del(`/project/${ident}`, {}).request(); const newProjects = projects.filter((project) => project.ident !== ident); From 96ce33c8e440877b704be09678d1aaac5b8317fb Mon Sep 17 00:00:00 2001 From: bnguyen-bcgsc Date: Wed, 27 Mar 2024 11:45:18 -0700 Subject: [PATCH 4/4] - DEVSU-2233 - Change snackbar notification timing when confirm dialog shows up while adding variant to key alterations - Update scss className to accurately reflect variant edit dialog className - Update spelling and formatting of confirm dialog --- app/components/VariantEditDialog/index.scss | 2 +- app/components/VariantEditDialog/index.tsx | 8 ++++---- app/hooks/useConfirmDialog.tsx | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/components/VariantEditDialog/index.scss b/app/components/VariantEditDialog/index.scss index 11c991e10..a76c1aea1 100644 --- a/app/components/VariantEditDialog/index.scss +++ b/app/components/VariantEditDialog/index.scss @@ -1,4 +1,4 @@ -.dialog { +.variant-edit-dialog { &__form-control { min-width: 160px; margin: 0 0 16px; diff --git a/app/components/VariantEditDialog/index.tsx b/app/components/VariantEditDialog/index.tsx index f6cecc67e..396ea1241 100644 --- a/app/components/VariantEditDialog/index.tsx +++ b/app/components/VariantEditDialog/index.tsx @@ -95,8 +95,8 @@ const VariantEditDialog = ({ } else { await req.request(); onClose({ ...editData }); + snackbar.success('Variant added to key alterations.'); } - snackbar.success('Variant added to key alterations.'); } catch (err) { showErrorSnackbar(`Error updating key alterations: ${err.message}`); onClose(); @@ -114,17 +114,17 @@ const VariantEditDialog = ({ }, [onClose]); return ( - + Key Alterations Edit -
+

Variant: {' '} {variant}

- + Add to Summary diff --git a/app/hooks/useConfirmDialog.tsx b/app/hooks/useConfirmDialog.tsx index b9c2e98ab..35839e0e5 100644 --- a/app/hooks/useConfirmDialog.tsx +++ b/app/hooks/useConfirmDialog.tsx @@ -38,7 +38,7 @@ const useConfirmDialog = () => { title="Confirm Action" text={textDict[report?.template.name]} confirmText="Yes" - cancelText="cancel" + cancelText="Cancel" />, document.getElementById('alert-dialog'), );