Skip to content

Commit e65e12f

Browse files
authored
Enh 835 support curation hints (#1080)
* feat: raised groups state and added snackbar hint for curation * feat: added hint to snackbar * fix: cypress test
1 parent 06a8857 commit e65e12f

File tree

11 files changed

+104
-63
lines changed

11 files changed

+104
-63
lines changed

compose/neurosynth-frontend/cypress/e2e/workflows/ingestion/Ingestion.cy.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('Ingestion', () => {
4444

4545
it('should show the dialog', () => {
4646
cy.login('mocked').visit(PATH);
47-
cy.contains('button', 'go to extraction').click(); // popup should open automatically as extraction has not been initialized in this mock
47+
cy.contains('button', 'start extraction').click(); // popup should open automatically as extraction has not been initialized in this mock
4848
cy.contains('button', 'NEXT').click();
4949

5050
cy.get('@baseStudiesFixture').its('request.body').should('not.have.a.property', 'doi');

compose/neurosynth-frontend/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ function App() {
6262
<SnackbarProvider
6363
ref={notistackRef}
6464
autoHideDuration={8000}
65+
anchorOrigin={{
66+
vertical: 'bottom',
67+
horizontal: 'right',
68+
}}
6569
action={(key) => (
6670
<IconButton onClick={handleCloseSnackbar(key)}>
6771
<Close sx={{ color: 'white' }} />

compose/neurosynth-frontend/src/hooks/projects/useGetProjectById.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const useGetProjectById = (projectId: string | undefined) => {
99
{
1010
select: (axiosResponse) => axiosResponse.data as INeurosynthProjectReturn,
1111
enabled: !!projectId,
12+
cacheTime: 0, // IMPORTANT: disable cache for this query as caching causes the project store to sometimes have old data when you switch between projects
1213
}
1314
);
1415
};

compose/neurosynth-frontend/src/pages/Curation/CurationPage.tsx

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { IProjectPageLocationState } from 'pages/Project/ProjectPage';
99
import {
1010
useGetProjectIsLoading,
1111
useProjectCreatedAt,
12+
useProjectCurationColumns,
1213
useProjectCurationIsPrisma,
1314
useProjectExtractionAnnotationId,
1415
useProjectExtractionStudysetId,
@@ -21,6 +22,8 @@ import CurationBoardAI from './components/CurationBoardAi';
2122
import CurationDownloadSummaryButton from './components/CurationDownloadSummaryButton';
2223
import PrismaDialog from './components/PrismaDialog';
2324
import LoadingStateIndicatorProject from 'components/LoadingStateIndicator/LoadingStateIndicatorProject';
25+
import { SnackbarKey, useSnackbar } from 'notistack';
26+
import useCurationBoardGroupsState from './hooks/useCurationBoardGroupsState';
2427

2528
const localStorageNewUIKey = 'show-new-ui-may-30-2025';
2629

@@ -30,10 +33,13 @@ const CurationPage: React.FC = () => {
3033
const studysetId = useProjectExtractionStudysetId();
3134
const canEdit = useUserCanEdit(projectUser || undefined);
3235
const { included, uncategorized } = useGetCurationSummary();
36+
const columns = useProjectCurationColumns();
3337
const annotationId = useProjectExtractionAnnotationId();
3438
const { data: studyset } = useGetStudysetById(studysetId || '', false);
3539
const { projectId } = useParams<{ projectId: string | undefined }>();
3640
const projectIsLoading = useGetProjectIsLoading();
41+
const { enqueueSnackbar, closeSnackbar } = useSnackbar();
42+
const { groups, selectedGroup, handleSetSelectedGroup } = useCurationBoardGroupsState();
3743

3844
const [prismaIsOpen, setPrismaIsOpen] = useState(false);
3945

@@ -59,8 +65,58 @@ const CurationPage: React.FC = () => {
5965
const extractionStepInitialized = studysetId && annotationId && (studyset?.studies?.length || 0) > 0;
6066
const canMoveToExtractionPhase = included > 0 && uncategorized === 0;
6167

68+
const handleNavigateToStep = (groupId: string, snackbarId: SnackbarKey) => {
69+
const foundGroup = groups.find((g) => g.id === groupId);
70+
if (foundGroup) {
71+
handleSetSelectedGroup(foundGroup);
72+
}
73+
closeSnackbar(snackbarId);
74+
};
75+
6276
const handleMoveToExtractionPhase = () => {
63-
if (extractionStepInitialized) {
77+
if (!canMoveToExtractionPhase) {
78+
const existingIssues: { phase: string; colId: string; numUncategorized: number }[] = [];
79+
for (let i = 0; i < columns.length - 1; i++) {
80+
// skip the last column as it is included
81+
const column = columns[i];
82+
const numUncategorized = column.stubStudies.filter((s) => s.exclusionTag === null).length;
83+
if (numUncategorized > 0) {
84+
existingIssues.push({ phase: column.name, colId: column.id, numUncategorized });
85+
}
86+
}
87+
88+
const snackbarId = enqueueSnackbar(
89+
<Box>
90+
<Typography variant="body1" sx={{ fontWeight: 'bold' }} gutterBottom>
91+
You must complete curation before moving to extraction
92+
</Typography>
93+
{existingIssues.map((issue) => (
94+
<Typography key={issue.phase} variant="body2">
95+
The <b>{issue.phase}</b> step still has <b>{issue.numUncategorized} studies</b> that need to
96+
be either <b>included</b> or <b>excluded</b>.
97+
</Typography>
98+
))}
99+
100+
<Box sx={{ marginTop: '1rem' }}>
101+
{existingIssues.map((issue) => (
102+
<Button
103+
key={issue.phase}
104+
sx={{ margin: '4px' }}
105+
variant="contained"
106+
color="primary"
107+
disableElevation
108+
size="small"
109+
onClick={() => handleNavigateToStep(issue.colId, snackbarId)}
110+
>
111+
Go to {issue.phase}
112+
</Button>
113+
))}
114+
</Box>
115+
</Box>,
116+
{ variant: 'warning', autoHideDuration: null }
117+
);
118+
return;
119+
} else if (extractionStepInitialized) {
64120
navigate(`/projects/${projectId}/extraction`);
65121
} else {
66122
navigate(`/projects/${projectId}/project`, {
@@ -73,6 +129,8 @@ const CurationPage: React.FC = () => {
73129
}
74130
};
75131

132+
const indicateGoToExtraction = !extractionStepInitialized && canMoveToExtractionPhase;
133+
76134
return (
77135
<StateHandlerComponent isError={false} isLoading={projectIsLoading}>
78136
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
@@ -161,28 +219,33 @@ const CurationPage: React.FC = () => {
161219
)}
162220
<Button
163221
onClick={handleMoveToExtractionPhase}
164-
variant="contained"
222+
variant={canMoveToExtractionPhase ? 'contained' : 'outlined'}
165223
color="success"
166224
size="small"
167225
sx={{
168226
ml: '0.5rem',
169227
fontSize: '12px',
170-
...(extractionStepInitialized || !canMoveToExtractionPhase
171-
? { color: 'white' }
172-
: {
173-
...GlobalStyles.colorPulseAnimation,
174-
color: 'success.dark',
175-
}),
228+
...(indicateGoToExtraction
229+
? { ...GlobalStyles.colorPulseAnimation, color: 'success.dark' }
230+
: {}),
176231
}}
177232
disableElevation
178-
disabled={!canEdit || !canMoveToExtractionPhase}
233+
disabled={!canEdit}
179234
>
180-
{extractionStepInitialized ? 'view extraction' : 'go to extraction'}
235+
{extractionStepInitialized ? 'view extraction' : 'start extraction'}
181236
</Button>
182237
</Box>
183238
</Box>
184239
<Box sx={{ height: '100%', overflow: 'hidden' }}>
185-
{useNewUI ? <CurationBoardAI /> : <CurationBoardBasic />}
240+
{useNewUI ? (
241+
<CurationBoardAI
242+
groups={groups}
243+
selectedGroup={selectedGroup}
244+
handleSetSelectedGroup={handleSetSelectedGroup}
245+
/>
246+
) : (
247+
<CurationBoardBasic />
248+
)}
186249
</Box>
187250
</Box>
188251
</StateHandlerComponent>

compose/neurosynth-frontend/src/pages/Curation/components/CurationBoardAi.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
import { Box } from '@mui/material';
2+
import InfoPopup from 'components/InfoPopup';
23
import { NAVBAR_HEIGHT } from 'components/Navbar/Navbar';
34
import { useSnackbar } from 'notistack';
4-
import CurationBoardAIGroupsList from 'pages/Curation/components/CurationBoardAIGroupsList';
5+
import CurationBoardAIGroupsList, { IGroupListItem } from 'pages/Curation/components/CurationBoardAIGroupsList';
56
import { useDeleteCurationImport } from 'pages/Project/store/ProjectStore';
6-
import useCurationBoardGroupsState from '../hooks/useCurationBoardGroupsState';
77
import CurationBoardAIInterfaceCurator from './CurationBoardAIInterfaceCurator';
88
import CurationBoardAIInterfaceExclude from './CurationBoardAIInterfaceExclude';
99
import CurationBoardAIInterfaceImportSummary from './CurationBoardAIInterfaceImportSummary';
10-
import InfoPopup from 'components/InfoPopup';
1110

1211
export enum ECurationBoardAIInterface {
1312
CURATOR = 'CURATOR', // basic curation interface with ability to toggle between spreadsheet and focused UIs.
1413
IMPORT_SUMMARY = 'IMPORT_SUMMARY', // see summary of your import (time, keywords, method, etc)
1514
EXCLUDE = 'EXCLUDE', // exclusion view
1615
}
1716

18-
const CurationBoardAI: React.FC = () => {
17+
const CurationBoardAI: React.FC<{
18+
groups: IGroupListItem[];
19+
selectedGroup: IGroupListItem | undefined;
20+
handleSetSelectedGroup: (group: IGroupListItem) => void;
21+
}> = ({ groups, selectedGroup, handleSetSelectedGroup }) => {
1922
const deleteCurationImport = useDeleteCurationImport();
2023
const { enqueueSnackbar } = useSnackbar();
21-
const { groups, selectedGroup, handleSetSelectedGroup } = useCurationBoardGroupsState();
2224

2325
const handleDeleteCurationImport = (importId: string) => {
2426
deleteCurationImport(importId);

compose/neurosynth-frontend/src/pages/Curation/hooks/useCurationBoardGroupsState.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,12 @@ function useCurationBoardGroupsState() {
4343
if (curationColumns.length === 0) return [];
4444
let groupListItems: IGroupListItem[] = [];
4545

46-
const numTotalStudies = curationColumns.reduce((acc, curr) => acc + curr.stubStudies.length, 0);
47-
4846
if (prismaConfig.isPrisma) {
4947
groupListItems.push({
5048
id: 'prisma_header',
5149
type: 'SUBHEADER',
5250
label: 'PRISMA Curation',
53-
count: numTotalStudies,
51+
count: null,
5452
UI: null,
5553
});
5654

@@ -142,7 +140,7 @@ function useCurationBoardGroupsState() {
142140
id: 'curate_header',
143141
type: 'SUBHEADER',
144142
label: 'Curation',
145-
count: numTotalStudies,
143+
count: null,
146144
UI: null,
147145
},
148146
{
@@ -239,7 +237,7 @@ function useCurationBoardGroupsState() {
239237
localStorage.setItem(selectedCurationStepLocalStorageKey, groups[1].id);
240238
setSelectedGroup(groups[1]);
241239
}
242-
}, [groups, curationColumns.length, selectedGroup, selectedCurationStepLocalStorageKey]);
240+
}, [groups, curationColumns.length, selectedGroup, selectedCurationStepLocalStorageKey, projectId]);
243241

244242
return {
245243
groups,

compose/neurosynth-frontend/src/pages/Project/components/ProjectEditMetaAnalyses.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,7 @@ const ProjectEditMetaAnalyses: React.FC = (props) => {
2424
const curationStepHasBeenInitialized = useProjectCurationColumns().length > 0;
2525

2626
const extractionStepHasBeenInitialized =
27-
!!extractionMetadata.annotationId &&
28-
!!extractionMetadata.studysetId &&
29-
(studyset?.studies?.length || 0) > 0;
27+
!!extractionMetadata.annotationId && !!extractionMetadata.studysetId && (studyset?.studies?.length || 0) > 0;
3028

3129
const { total, included, uncategorized } = useGetCurationSummary();
3230
const disableExtractionStep =
@@ -41,10 +39,7 @@ const ProjectEditMetaAnalyses: React.FC = (props) => {
4139

4240
return (
4341
<Stepper activeStep={activeStep} orientation="vertical" sx={[ProjectPageStyles.stepper]}>
44-
<ProjectCurationStep
45-
disabled={!canEdit}
46-
curationStepHasBeenInitialized={curationStepHasBeenInitialized}
47-
/>
42+
<ProjectCurationStep disabled={!canEdit} curationStepHasBeenInitialized={curationStepHasBeenInitialized} />
4843
<ProjectExtractionStep
4944
extractionStepHasBeenInitialized={extractionStepHasBeenInitialized}
5045
disabled={!canEdit || disableExtractionStep}

compose/neurosynth-frontend/src/pages/Project/components/ProjectExtractionStep.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ const ProjectExtractionStep: React.FC<IExtractionStep & StepProps> = (props) =>
3535
<Box sx={{ marginLeft: '2rem' }}>
3636
<Typography sx={{ color: 'muted.main' }}>
3737
<b>
38-
You have completed your study curation, and now have a potential list of
39-
studies to include in your meta-analysis
38+
You have completed your study curation, and now have a potential list of studies to include
39+
in your meta-analysis
4040
</b>
4141
</Typography>
4242
<Typography gutterBottom sx={{ color: 'muted.main' }}>
43-
In this step, add necessary study data to the studies in your studyset (like
44-
coordinates and metadata) as well as analysis annotations that will be used
45-
to help filter analyses within your studies
43+
In this step, add necessary study data to the studies in your studyset (like coordinates and
44+
metadata) as well as analysis annotations that will be used to help filter analyses within your
45+
studies
4646
</Typography>
4747
<Box sx={{ marginTop: '1rem' }}>
4848
{extractionStepHasBeenInitialized ? (

compose/neurosynth-frontend/src/pages/Project/components/ProjectExtractionStepCard.tsx

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,7 @@
11
import BookmarkIcon from '@mui/icons-material/Bookmark';
22
import CheckIcon from '@mui/icons-material/Check';
33
import QuestionMarkIcon from '@mui/icons-material/QuestionMark';
4-
import {
5-
Box,
6-
Card,
7-
CardContent,
8-
Typography,
9-
CircularProgress,
10-
CardActions,
11-
Button,
12-
} from '@mui/material';
4+
import { Box, Card, CardContent, Typography, CircularProgress, CardActions, Button } from '@mui/material';
135
import ConfirmationDialog from 'components/Dialogs/ConfirmationDialog';
146
import ProjectComponentsStyles from 'pages/Project/components/Project.styles';
157
import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent';
@@ -30,10 +22,7 @@ const getPercentageComplete = (extractionSummary: IExtractionSummary): number =>
3022
};
3123

3224
const ProjectExtractionStepCard: React.FC<{ disabled: boolean }> = ({ disabled }) => {
33-
const [
34-
markAllAsCompleteConfirmationDialogIsOpen,
35-
setMarkAllAsCompleteConfirmationDialogIsOpen,
36-
] = useState(false);
25+
const [markAllAsCompleteConfirmationDialogIsOpen, setMarkAllAsCompleteConfirmationDialogIsOpen] = useState(false);
3726
const studysetId = useProjectExtractionStudysetId();
3827
const { projectId } = useParams<{ projectId: string }>();
3928
const extractionSummary = useGetExtractionSummary(projectId);
@@ -55,9 +44,7 @@ const ProjectExtractionStepCard: React.FC<{ disabled: boolean }> = ({ disabled }
5544
};
5645

5746
const allStudiesAreComplete = useMemo(() => {
58-
return (
59-
extractionSummary.total > 0 && extractionSummary.completed === extractionSummary.total
60-
);
47+
return extractionSummary.total > 0 && extractionSummary.completed === extractionSummary.total;
6148
}, [extractionSummary.completed, extractionSummary.total]);
6249

6350
return (
@@ -70,10 +57,7 @@ const ProjectExtractionStepCard: React.FC<{ disabled: boolean }> = ({ disabled }
7057
padding: '8px',
7158
}}
7259
>
73-
<StateHandlerComponent
74-
isError={getStudysetIsError}
75-
isLoading={getStudysetIsLoading}
76-
>
60+
<StateHandlerComponent isError={getStudysetIsError} isLoading={getStudysetIsLoading}>
7761
<CardContent>
7862
<Box sx={ProjectComponentsStyles.stepTitle}>
7963
<Typography sx={{ color: 'muted.main' }}>
@@ -83,11 +67,7 @@ const ProjectExtractionStepCard: React.FC<{ disabled: boolean }> = ({ disabled }
8367
sx={ProjectComponentsStyles.progressCircle}
8468
variant="determinate"
8569
value={getPercentageComplete(extractionSummary)}
86-
color={
87-
getPercentageComplete(extractionSummary) === 100
88-
? 'success'
89-
: 'secondary'
90-
}
70+
color={getPercentageComplete(extractionSummary) === 100 ? 'success' : 'secondary'}
9171
/>
9272
</Box>
9373
<Typography gutterBottom variant="h5" sx={{ marginRight: '40px' }}>

compose/neurosynth-frontend/src/pages/Project/store/ProjectStore.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
setGivenStudyStatusesAsCompleteHelper,
2121
updateStubFieldHelper,
2222
} from 'pages/Project/store/ProjectStore.helpers';
23-
import { useEffect } from 'react';
23+
import { useEffect, useState } from 'react';
2424
import { useQueryClient } from 'react-query';
2525
import { useParams } from 'react-router-dom';
2626
import API from 'utils/api';

0 commit comments

Comments
 (0)