diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/BaseStudyPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/BaseStudyPage.cy.tsx index bd1a7dfe..7c2b320d 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/BaseStudyPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/BaseStudyPage.cy.tsx @@ -7,7 +7,7 @@ const PAGE_NAME = 'BaseStudyPage'; describe(PAGE_NAME, () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', `https://api.semanticscholar.org/**`, { fixture: 'semanticScholar', }).as('semanticScholarFixture'); diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/EditStudyPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/EditStudyPage.cy.tsx index 586d7573..cbe97e57 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/EditStudyPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/EditStudyPage.cy.tsx @@ -7,7 +7,7 @@ const PAGE_NAME = 'EditStudyPage'; describe(PAGE_NAME, () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('GET', `https://api.semanticscholar.org/**`, { fixture: 'semanticScholar', diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/LandingPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/LandingPage.cy.tsx index cd6307ab..06a083d0 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/LandingPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/LandingPage.cy.tsx @@ -6,7 +6,7 @@ const PAGE_NAME = 'LandingPage'; describe(PAGE_NAME, () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('GET', `**/api/base-studies/**`, { fixture: 'baseStudies/baseStudiesNoResults', diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/MetaAnalysisPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/MetaAnalysisPage.cy.tsx index 7c045940..4b0f70bd 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/MetaAnalysisPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/MetaAnalysisPage.cy.tsx @@ -7,7 +7,7 @@ const PAGE_NAME = 'MetaAnalysisPage'; describe(PAGE_NAME, () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('GET', `**/api/specifications/**`, { fixture: 'specification' }).as( 'specificationFixture' diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/ProjectMetaAnalysesPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/ProjectMetaAnalysesPage.cy.tsx index a84f002d..e9cdb41b 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/ProjectMetaAnalysesPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/ProjectMetaAnalysesPage.cy.tsx @@ -7,7 +7,7 @@ const PAGE_NAME = 'ProjectMetaAnalysesPage'; describe(PAGE_NAME, () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('GET', `**/api/specifications/**`, { fixture: 'specification' }).as( 'specificationFixture' diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/ProjectPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/ProjectPage.cy.tsx index 3631c434..ebd266a0 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/ProjectPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/ProjectPage.cy.tsx @@ -7,7 +7,7 @@ const PAGE_NAME = 'ProjectPage'; describe(PAGE_NAME, () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('POST', `https://www.google-analytics.com/*/**`, {}).as( 'googleAnalyticsFixture' diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/PublicStudiesPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/PublicStudiesPage.cy.tsx index dc4a824d..0cf70328 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/PublicStudiesPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/PublicStudiesPage.cy.tsx @@ -9,7 +9,7 @@ const PAGE_NAME = 'StudiesPage'; describe.skip(PAGE_NAME, () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); }); diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportStudiesDialog.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportStudiesDialog.cy.tsx index 607dfadf..c7dfde86 100644 --- a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportStudiesDialog.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportStudiesDialog.cy.tsx @@ -2,7 +2,7 @@ describe('ImportStudiesDialog', () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('GET', `**/api/projects/*`, { fixture: 'projects/projectExtractionStep', @@ -198,14 +198,14 @@ describe('ImportStudiesDialog', () => { }); it('should set the source and show the input', () => { - cy.get('input[role="combobox"]').click(); + cy.get('input[role="combobox"').click(); cy.contains('li', 'Scopus').click(); cy.get('textarea').should('be.visible'); cy.contains(/Input is empty/).should('be.visible'); }); it('should set the sources and enable the next button', () => { - cy.get('input[role="combobox"]').click(); + cy.get('input[role="combobox"').click(); cy.contains('li', 'Scopus').click(); cy.get('textarea[placeholder="paste in valid endnote, bibtex, or RIS syntax"]') .click() @@ -220,7 +220,7 @@ describe('ImportStudiesDialog', () => { }); it('should show an error message', () => { - cy.get('input[role="combobox"]').click(); + cy.get('input[role="combobox"').click(); cy.contains('li', 'Scopus').click(); cy.get('textarea[placeholder="paste in valid endnote, bibtex, or RIS syntax"]').type( 'INVALID FORMAT' @@ -229,7 +229,7 @@ describe('ImportStudiesDialog', () => { }); it('should import studies', () => { - cy.get('input[role="combobox"]').click(); + cy.get('input[role="combobox"').click(); cy.contains('li', 'Scopus').click(); cy.get('textarea[placeholder="paste in valid endnote, bibtex, or RIS syntax"]') .click() @@ -249,7 +249,7 @@ describe('ImportStudiesDialog', () => { }); it('should upload a onenote (ENW) file', () => { - cy.get('input[role="combobox"]').click(); + cy.get('input[role="combobox"').click(); cy.contains('li', 'Scopus').click(); cy.get('label[role="button"]').selectFile( 'cypress/fixtures/standardFiles/onenoteStudies.txt' diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/Extraction/ExtractionTable.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/Extraction/ExtractionTable.cy.tsx deleted file mode 100644 index 9a75dd51..00000000 --- a/compose/neurosynth-frontend/cypress/e2e/workflows/Extraction/ExtractionTable.cy.tsx +++ /dev/null @@ -1,693 +0,0 @@ -/// - -import { INeurosynthProjectReturn } from 'hooks/projects/useGetProjects'; -import { StudyReturn, StudysetReturn } from 'neurostore-typescript-sdk'; -import { IExtractionTableStudy } from 'pages/Extraction/components/ExtractionTable'; - -describe('ExtractionTable', () => { - beforeEach(() => { - cy.clearLocalStorage(); - cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); - cy.intercept('GET', `**/api/projects/*`, { - fixture: 'Extraction/project', - }).as('projectFixture'); - cy.intercept('GET', `**/api/studysets/*`, { fixture: 'studyset' }).as('studysetFixture'); - - cy.intercept('PUT', `**/api/projects/*`, { fixture: 'Extraction/project' }).as( - 'updateProjectFixture' - ); - - cy.intercept('GET', `**/api/studies/*`, { fixture: 'study' }).as('studyFixture'); - cy.intercept('GET', `**/api/annotations/*`, { fixture: 'annotation' }).as( - 'annotationsFixture' - ); - - cy.intercept('GET', `https://api.semanticscholar.org/**`, { - fixture: 'semanticScholar', - }).as('semanticScholarFixture'); - }); - - describe('Filtering', () => { - beforeEach(() => { - cy.login('mocked').visit(`/projects/abc123/extraction`); - }); - - it('should filter the table by year', () => { - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture?.response?.body as StudysetReturn; - const studysetStudies = studyset.studies as StudyReturn[]; - cy.get('input').eq(0).click(); - cy.get(`input`) - .eq(0) - .type(studysetStudies[0].year?.toString() || ''); - }); - - cy.get('tbody > tr').should('have.length', 1); - }); - - it('should filter the table by name', () => { - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture?.response?.body as StudysetReturn; - const studysetStudies = studyset.studies as StudyReturn[]; - cy.get('input').eq(1).click(); - cy.get(`input`) - .eq(1) - .type(studysetStudies[0].name || ''); - }); - - cy.get('tbody > tr').should('have.length', 1); - }); - - it('should filter the table by author', () => { - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture?.response?.body as StudysetReturn; - const studysetStudies = studyset.studies as StudyReturn[]; - cy.get('input').eq(2).click(); - cy.get(`input`) - .eq(2) - .type(studysetStudies[0].authors || ''); - }); - - cy.get('tbody > tr').should('have.length', 1); - }); - - it('should show available journals as options', () => { - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture?.response?.body as StudysetReturn; - const studysetStudies = studyset.studies as StudyReturn[]; - const uniqueJouranls = new Set(); - studysetStudies.forEach((study) => { - if (study.publication) uniqueJouranls.add(study.publication); - }); - cy.get('input').eq(3).click(); - cy.get('div[role="presentation"]').within(() => { - cy.get('li').should('have.length', uniqueJouranls.size); - }); - }); - }); - - it('should filter the table by journal', () => { - cy.get('input').eq(3).click(); - cy.get('div[role="presentation"]').within((menu) => { - cy.get('li').eq(0).click(); - }); - - cy.get('tbody > tr').should('have.length', 1); - }); - - it('should filter the table by doi', () => { - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture?.response?.body as StudysetReturn; - const studysetStudies = studyset.studies as StudyReturn[]; - cy.get('input').eq(4).click(); - cy.get(`input`) - .eq(4) - .type(studysetStudies[0].doi || ''); - }); - - cy.get('tbody > tr').should('have.length', 1); - }); - - it('should filter the table by pmid', () => { - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture?.response?.body as StudysetReturn; - const studysetStudies = studyset.studies as StudyReturn[]; - cy.get('input').eq(5).click(); - cy.get(`input`) - .eq(5) - .type(studysetStudies[0].pmid || ''); - }); - - cy.get('tbody > tr').should('have.length', 1); - }); - - it('should filter the table by status', () => { - cy.get('div[role="combobox"]').eq(0).click(); - cy.get('div[role="presentation"]').within(() => { - // set to completed - cy.get('li').eq(3).click(); - }); - - cy.get('tbody > tr').should('have.length', 1); - }); - - it('should show filtering chips at the bottom if one or more filters are applied', () => { - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture?.response?.body as StudysetReturn; - const studysetStudies = studyset.studies as StudyReturn[]; - cy.get('input').eq(0).click(); - cy.get(`input`) - .eq(0) - .type(studysetStudies[0].year?.toString() || ''); - cy.get('input').eq(1).click(); - cy.get('input') - .eq(1) - .type(studysetStudies[0].name?.toString() || ''); - - cy.contains(`Filtering YEAR: ${studysetStudies[0].year?.toString()}`).should( - 'exist' - ); - cy.contains(`Filtering NAME: ${studysetStudies[0].name?.toString()}`).should( - 'exist' - ); - }); - }); - - it('should remove the filter if the delete button is clicked', () => { - // ARRANGE - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture?.response?.body as StudysetReturn; - const studysetStudies = studyset.studies as StudyReturn[]; - cy.get('input').eq(0).click(); - cy.get(`input`) - .eq(0) - .type(studysetStudies[0].year?.toString() || ''); - }); - cy.get('tbody > tr').should('have.length', 1); - cy.get('[data-testid="CancelIcon"]').should('exist'); - // ACT - cy.get('[data-testid="CancelIcon"]').click(); - - // ASSERT - cy.get('tbody > tr').should('have.length', 3); - cy.get(`[data-testid="CancelIcon"]`).should('not.exist'); - }); - }); - - describe('status', () => { - beforeEach(() => { - cy.login('mocked').visit(`/projects/abc123/extraction`); - }); - - it('should change the study status', () => { - // ARRANGE - cy.get('tbody > tr').eq(0).get('td').eq(6).as('getFirstRowStudyStatusCol'); - cy.get('@getFirstRowStudyStatusCol').within(() => { - cy.get('button').eq(0).should('have.class', 'MuiButton-contained'); - }); - - // ACT - cy.get('@getFirstRowStudyStatusCol').within(() => { - cy.get('button').eq(1).click(); - }); - - // ASSERT - cy.get('@getFirstRowStudyStatusCol').within(() => { - cy.get('button').eq(0).should('have.class', 'MuiButton-outlined'); - cy.get('button').eq(1).should('have.class', 'MuiButton-contained'); - }); - }); - }); - - describe('sorting', () => { - beforeEach(() => { - cy.login('mocked').visit(`/projects/abc123/extraction`); - }); - - it('should sort by year desc', () => { - cy.contains('Year').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort( - (a, b) => (b.year as number) - (a.year as number) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td') - .eq(0) - .should('have.text', sortedStudies[index].year?.toString()); - }); - }); - }); - }); - - it('should sort by year asc', () => { - cy.contains('Year').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').click(); - cy.get('[data-testid="ArrowUpwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort( - (a, b) => (a.year as number) - (b.year as number) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td') - .eq(0) - .should('have.text', sortedStudies[index].year?.toString()); - }); - }); - }); - }); - - it('should sort by name asc', () => { - cy.contains('Name').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (b.name as string).localeCompare(a.name as string) - ); - - console.log(sortedStudies); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td').eq(1).should('have.text', sortedStudies[index].name); - }); - }); - }); - }); - - it('should sort by name desc', () => { - cy.contains('Name').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').click(); - cy.get('[data-testid="ArrowUpwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (a.name as string).localeCompare(b.name as string) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td').eq(1).should('have.text', sortedStudies[index].name); - }); - }); - }); - }); - - it('should sort by authors desc', () => { - cy.contains('Authors').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (b.authors as string).localeCompare(a.authors as string) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td').eq(2).should('have.text', sortedStudies[index].authors); - }); - }); - }); - }); - - it('should sort by authors asc', () => { - cy.contains('Authors').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').click(); - cy.get('[data-testid="ArrowUpwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (a.authors as string).localeCompare(b.authors as string) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td').eq(2).should('have.text', sortedStudies[index].authors); - }); - }); - }); - }); - - it('should sort by journal desc', () => { - cy.contains('Journal').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (b.publication as string).localeCompare(a.publication as string) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td').eq(3).should('have.text', sortedStudies[index].publication); - }); - }); - }); - }); - - it('should sort by journal desc', () => { - cy.contains('Journal').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').click(); - cy.get('[data-testid="ArrowUpwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (a.publication as string).localeCompare(b.publication as string) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td').eq(3).should('have.text', sortedStudies[index].publication); - }); - }); - }); - }); - - it('should sort by doi desc', () => { - cy.contains('DOI').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (b.doi as string).localeCompare(a.doi as string) - ); - - console.log(sortedStudies); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td').eq(4).should('have.text', sortedStudies[index].doi); - }); - }); - }); - }); - it('should sort by doi asc', () => { - cy.contains('DOI').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').click(); - cy.get('[data-testid="ArrowUpwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (a.doi as string).localeCompare(b.doi as string) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td').eq(4).should('have.text', sortedStudies[index].doi); - }); - }); - }); - }); - - it('should sort by pmid desc', () => { - cy.contains('PMID').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (b?.pmid || '').localeCompare(a?.pmid || '', undefined, { - numeric: true, - }) - ); - - console.log(sortedStudies); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td') - .eq(5) - .should('have.text', sortedStudies[index].pmid ?? ''); - }); - }); - }); - }); - - it('should sort by pmid asc', () => { - cy.contains('PMID').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').click(); - cy.get('[data-testid="ArrowUpwardIcon"]').should('exist'); - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies = studies.sort((a, b) => - (a?.pmid || '').localeCompare(b?.pmid || '', undefined, { - numeric: true, - }) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td') - .eq(5) - .should('have.text', sortedStudies[index].pmid ?? ''); - }); - }); - }); - }); - - it('should sort by status desc', () => { - cy.contains('Status').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').should('exist'); - - cy.wait('@projectFixture').then((projectFixture) => { - const project = projectFixture?.response?.body as INeurosynthProjectReturn; - const studyStatusList = project.provenance.extractionMetadata.studyStatusList; - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies: IExtractionTableStudy[] = studies - .map((study) => ({ - ...study, - status: studyStatusList.find((status) => status.id === study.id) - ?.status, - })) - .sort((a, b) => - (a?.status || '').localeCompare(b?.status || '', undefined, { - numeric: true, - }) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td') - .eq(6) - .within(() => { - const studyStatus = sortedStudies[index].status; - const buttonIndex = - studyStatus === 'completed' - ? 2 - : studyStatus === 'savedforlater' - ? 1 - : 0; - - cy.get('button').each((button, index) => { - if (index === buttonIndex) { - cy.wrap(button).should( - 'have.class', - 'MuiButton-contained' - ); - } else { - cy.wrap(button).should( - 'have.class', - 'MuiButton-outlined' - ); - } - }); - }); - }); - }); - }); - }); - }); - - it('should sort by status asc', () => { - cy.contains('Status').click(); - cy.get('[data-testid="ArrowDownwardIcon"]').click(); - cy.get('[data-testid="ArrowUpwardIcon"]').should('exist'); - - cy.wait('@projectFixture').then((projectFixture) => { - const project = projectFixture?.response?.body as INeurosynthProjectReturn; - const studyStatusList = project.provenance.extractionMetadata.studyStatusList; - - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - const studies = [...(studyset.studies || [])] as StudyReturn[]; - - const sortedStudies: IExtractionTableStudy[] = studies - .map((study) => ({ - ...study, - status: studyStatusList.find((status) => status.id === study.id) - ?.status, - })) - .sort((a, b) => - (b?.status || '').localeCompare(a?.status || '', undefined, { - numeric: true, - }) - ); - - cy.get('tbody > tr').each((tr, index) => { - cy.wrap(tr).within(() => { - cy.get('td') - .eq(6) - .within(() => { - const studyStatus = sortedStudies[index].status; - const buttonIndex = - studyStatus === 'completed' - ? 2 - : studyStatus === 'savedforlater' - ? 1 - : 0; - - cy.get('button').each((button, index) => { - if (index === buttonIndex) { - cy.wrap(button).should( - 'have.class', - 'MuiButton-contained' - ); - } else { - cy.wrap(button).should( - 'have.class', - 'MuiButton-outlined' - ); - } - }); - }); - }); - }); - }); - }); - }); - }); - - describe.only('pagination', () => { - beforeEach(() => { - cy.fixture('studyset').then((studyset) => { - // as we are artificially creating new studies below, the out of sync popup wil appear. That's expected and - // we can just ignore it during out test - console.log(studyset); - const studies = []; - for (let i = 0; i < 100; i++) { - studies.push(...(studyset?.studies as StudyReturn[])); - } - studyset.studies = studies; - console.log(studyset); - cy.intercept('GET', `**/api/studysets/*`, studyset).as('studysetFixture'); - }); - }); - - beforeEach(() => { - cy.login('mocked').visit(`/projects/abc123/extraction`); - }); - - it('should give the correct number of studies', () => { - cy.wait('@studysetFixture').then((studysetFixture) => { - const studyset = studysetFixture.response?.body as StudysetReturn; - cy.contains(`Total: ${studyset.studies?.length} studies`).should('exist'); - }); - }); - - it('should change the number of rows per page', () => { - cy.get('[role="combobox"]').eq(2).click(); - cy.get('div[role="presentation"]').within(() => { - cy.contains('10').click(); - }); - cy.get('tbody > tr').should('have.length', 10); - cy.get('.MuiPaginationItem-root').contains('30'); - - cy.get('[role="combobox"]').eq(2).click(); - cy.get('div[role="presentation"]').within(() => { - cy.contains('25').click(); - }); - cy.get('tbody > tr').should('have.length', 25); - cy.get('.MuiPaginationItem-root').contains('12'); - - cy.get('[role="combobox"]').eq(2).click(); - cy.get('div[role="presentation"]').within(() => { - cy.contains('50').click(); - }); - cy.get('tbody > tr').should('have.length', 50); - - cy.get('[role="combobox"]').eq(2).click(); - cy.get('div[role="presentation"]').within(() => { - cy.contains('100').click(); - }); - cy.get('tbody > tr').should('have.length', 100); - cy.get('.MuiPaginationItem-root').contains('3'); - }); - }); - - describe('navigation', () => { - beforeEach(() => { - cy.login('mocked').visit(`/projects/abc123/extraction`); - }); - - it('should navigate to the selected study with the saved table state', () => { - cy.get('tbody > tr').eq(0).click(); - cy.url().should('include', `/projects/abc123/extraction/studies/3Jvrv4Pct3hb/edit`); - - cy.window().then((window) => { - const item = window.sessionStorage.getItem(`abc123-extraction-table`); - cy.wrap(item).should('exist'); - }); - }); - - it('should keep the table state', () => { - cy.get('tbody > tr').eq(0).click(); - cy.url().should('include', `/projects/abc123/extraction/studies/3Jvrv4Pct3hb/edit`); - - cy.visit(`/projects/abc123/extraction`); - - cy.window().then((window) => { - const item = window.sessionStorage.getItem(`abc123-extraction-table`); - cy.wrap(item).should('exist'); - }); - }); - - it('should save the filter and sorting to the table state', () => { - cy.contains('Year').click(); - cy.get('input').eq(1).click(); - cy.get(`input`).eq(1).type('Activation'); - - // we wait because of the debounce - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(800); - - cy.get('tbody > tr').eq(0).click(); - - cy.window().then((window) => { - const state = window.sessionStorage.getItem(`abc123-extraction-table`); - const parsedState = JSON.parse(state || '{}'); - console.log(parsedState); - cy.wrap(parsedState).should('deep.equal', { - columnFilters: [{ id: 'name', value: 'Activation' }], - sorting: [{ id: 'year', desc: true }], - studies: ['3zutS8kyg2sy'], - }); - }); - }); - }); -}); diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/MetaAnalyses/CreateSpecificationDialog.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/MetaAnalyses/CreateSpecificationDialog.cy.tsx index 7e42430c..0be5448e 100644 --- a/compose/neurosynth-frontend/cypress/e2e/workflows/MetaAnalyses/CreateSpecificationDialog.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/MetaAnalyses/CreateSpecificationDialog.cy.tsx @@ -2,7 +2,7 @@ describe('CreateSpecificationDialog', () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('GET', `**/api/meta-analyses*`, { fixture: 'metaAnalyses' }).as( 'metaAnalysesFixture' diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/SleuthImport/DoSleuthImport.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/SleuthImport/DoSleuthImport.cy.tsx index c25014cf..516a357c 100644 --- a/compose/neurosynth-frontend/cypress/e2e/workflows/SleuthImport/DoSleuthImport.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/SleuthImport/DoSleuthImport.cy.tsx @@ -14,7 +14,7 @@ describe('DoSleuthImport', () => { const neurosynthAPIBaseURL = Cypress.env('neurosynthAPIBaseURL'); beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('POST', `https://www.google-analytics.com/*/**`, {}).as( diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/ingestion/Ingestion.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/ingestion/Ingestion.cy.tsx index fb90cc61..6a2d6ab7 100644 --- a/compose/neurosynth-frontend/cypress/e2e/workflows/ingestion/Ingestion.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/ingestion/Ingestion.cy.tsx @@ -4,7 +4,7 @@ const PATH = '/projects/mock-project-id/curation'; describe('Ingestion', () => { beforeEach(() => { - cy.clearLocalStorage(); + cy.clearLocalStorage().clearSessionStorage(); cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); cy.intercept('GET', `**/api/meta-analyses*`, { fixture: 'metaAnalyses' }).as( 'metaAnalysesFixture' diff --git a/compose/neurosynth-frontend/cypress/fixtures/Extraction/project.json b/compose/neurosynth-frontend/cypress/fixtures/Extraction/project.json deleted file mode 100644 index c7d4209e..00000000 --- a/compose/neurosynth-frontend/cypress/fixtures/Extraction/project.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "created_at": "2023-11-02T15:48:04.630172+00:00", - "description": "this is a bulk import test", - "id": "abc123", - "meta_analyses": ["3xHJJQWFURob"], - "name": "Bulk import test", - "neurostore_study": { - "created_at": "2023-11-02T15:48:04.640598+00:00", - "exception": null, - "neurostore_id": "7BrAuwJ2tjPW", - "status": "PENDING", - "traceback": null, - "updated_at": "2023-11-02T15:48:04.653465+00:00" - }, - "neurostore_url": "https://neurostore.org/api/studies/7BrAuwJ2tjPW", - "provenance": { - "curationMetadata": { - "columns": [ - { - "id": "370ba40c-91e8-47e7-9b4d-926bc8f31d10", - "name": "not included", - "stubStudies": [] - }, - { - "id": "1f15d9a7-968e-44a6-8574-caa3794e050e", - "name": "included", - "stubStudies": [ - { - "abstractText": "", - "articleLink": "https://pubmed.ncbi.nlm.nih.gov/9808463", - "articleYear": "1998", - "authors": "Corbetta M, Akbudak E, Conturo TE, Snyder AZ, Ollinger JM, Drury HA, Linenweber MR, Petersen SE, Raichle ME, Van Essen DC, Shulman GL", - "doi": "10.1016/S0896-6273(00)80593-0", - "exclusionTag": null, - "id": "1", - "identificationSource": { - "id": "neurosynth_neurostore_id_source", - "label": "Neurostore" - }, - "journal": "Neuron", - "keywords": "", - "neurostoreId": "3Jvrv4Pct3hb", - "pmcid": "", - "pmid": "9808463", - "searchTerm": "?genericSearchStr=psychosis&dataType=all&source=all&sortBy=relevance&descOrder=true", - "tags": [], - "title": "A common network of functional areas for attention and eye movements." - }, - { - "abstractText": "Important decisions are often made under stressful\r\ncircumstances thatmight compromise self-regulatory\r\nbehavior. Yet the neural mechanisms by which\r\nstress influences self-control choices are unclear.\r\nWe investigated these mechanisms in human participants\r\nwho faced self-control dilemmas over food\r\nreward while undergoing fMRI following stress.\r\nWe found that stress increased the influence of\r\nimmediately rewarding taste attributes on choice\r\nand reduced self-control. This choice pattern was\r\naccompanied by increased functional connectivity\r\nbetween ventromedial prefrontal cortex (vmPFC)\r\nand amygdala and striatal regions encoding tastiness.\r\nFurthermore, stress was associated with\r\nreduced connectivity between the vmPFC and\r\ndorsolateral prefrontal cortex regions linked to selfcontrol\r\nsuccess. Notably, alterations in connectivity\r\npathways could be dissociated by their differential\r\nrelationships with cortisol and perceived stress.\r\nOur results indicate that stress may compromise\r\nself-control decisions by both enhancing the impact\r\nof immediately rewarding attributes and reducing the\r\nefficacy of regions promoting behaviors that are\r\nconsistent with long-term goals.\r\n\r\nThis collection contains second level correlations of stress induced differences in the influence of taste based decisions on vStr,Amyg and vmPFC and their connectivity.\r\n\r\nKey words: food choice, decision making, self-regulation", - "articleLink": "", - "articleYear": "", - "authors": "Silvia U. Maier, Aidan B. Makwana and Todd A. Hare", - "doi": "10.1016/j.neuron.2015.07.005", - "exclusionTag": null, - "id": "2", - "identificationSource": { - "id": "neurosynth_neurostore_id_source", - "label": "Neurostore" - }, - "journal": "Neuron", - "keywords": "", - "neurostoreId": "36nHEsLLPwBB", - "pmcid": "", - "pmid": "26247866", - "searchTerm": "?genericSearchStr=psychosis&dataType=all&source=all&sortBy=relevance&descOrder=true", - "tags": [], - "title": "Acute Stress Impairs Self-Control in Goal-Directed Choice by Altering Multiple Functional Connections within the Brain’s Decision Circuits" - }, - { - "abstractText": "", - "articleLink": "", - "articleYear": "", - "authors": "Ethan M. McCormick, Yang Qu and Eva H. Telzer", - "doi": "10.3389/fnhum.2017.00141", - "exclusionTag": null, - "id": "3", - "identificationSource": { - "id": "neurosynth_neurostore_id_source", - "label": "Neurostore" - }, - "journal": "Frontiers in Human Neuroscience", - "keywords": "", - "neurostoreId": "3zutS8kyg2sy", - "pmcid": "", - "pmid": "", - "searchTerm": "?genericSearchStr=psychosis&dataType=all&source=all&sortBy=relevance&descOrder=true", - "tags": [], - "title": "Activation in Context: Differential Conclusions Drawn from Cross-Sectional and Longitudinal Analyses of Adolescents’ Cognitive Control-Related Neural Activity" - } - ] - } - ], - "exclusionTags": [ - { - "id": "neurosynth_exclude_exclusion", - "isAssignable": true, - "isExclusionTag": true, - "label": "Exclude" - }, - { - "id": "neurosynth_duplicate_exclusion", - "isAssignable": true, - "isExclusionTag": true, - "label": "Duplicate" - } - ], - "identificationSources": [ - { - "id": "neurosynth_neurostore_id_source", - "label": "Neurostore" - }, - { - "id": "neurosynth_pubmed_id_source", - "label": "PubMed" - }, - { - "id": "neurosynth_scopus_id_source", - "label": "Scopus" - }, - { - "id": "neurosynth_web_of_science_id_source", - "label": "Web of Science" - }, - { - "id": "neurosynth_psycinfo_id_source", - "label": "PsycInfo" - } - ], - "infoTags": [ - { - "id": "neurosynth_untagged_tag", - "isAssignable": false, - "isExclusionTag": false, - "label": "Untagged studies" - }, - { - "id": "neurosynth_uncategorized_tag", - "isAssignable": false, - "isExclusionTag": false, - "label": "Uncategorized Studies" - }, - { - "id": "neurosynth_needs_review_tag", - "isAssignable": false, - "isExclusionTag": false, - "label": "Needs Review" - }, - { - "id": "6f299c47-766c-48bd-a56b-9c77019ea9de", - "isAssignable": true, - "isExclusionTag": false, - "label": "marijuana" - } - ], - "prismaConfig": { - "eligibility": { - "exclusionTags": [] - }, - "identification": { - "exclusionTags": [] - }, - "isPrisma": false, - "screening": { - "exclusionTags": [] - } - } - }, - "extractionMetadata": { - "annotationId": "5LSBDTGqA6RF", - "studyStatusList": [ - { "id": "3zutS8kyg2sy", "status": "completed" } - ], - "studysetId": "73HRs8HaJbR8" - }, - "metaAnalysisMetadata": { - "canEditMetaAnalyses": false - } - }, - "public": false, - "updated_at": "2023-11-02T19:42:04.265234+00:00", - "user": "auth0|62e0e6c9dd47048572613b4d", - "username": "Test User" -} diff --git a/compose/neurosynth-frontend/cypress/fixtures/studyset.json b/compose/neurosynth-frontend/cypress/fixtures/studyset.json index 5f75b2f6..0335eaf3 100644 --- a/compose/neurosynth-frontend/cypress/fixtures/studyset.json +++ b/compose/neurosynth-frontend/cypress/fixtures/studyset.json @@ -1,606 +1,660 @@ { - "id": "5ATjENA3VVyE", - "name": "a test studyset", - "user": "auth0|62de78bc11222b208cd022c8", - "description": "some new studyset", - "publication": null, - "doi": null, - "pmid": null, - "created_at": "2022-07-25T11:08:25.999097+00:00", - "updated_at": null, - "studies": [ + "id":"5ATjENA3VVyE", + "name":"a test studyset", + "user":"auth0|62de78bc11222b208cd022c8", + "description":"some new studyset", + "publication":null, + "doi":null, + "pmid":null, + "created_at":"2022-07-25T11:08:25.999097+00:00", + "updated_at":null, + "studies":[ { - "id": "3Jvrv4Pct3hb", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "user": null, - "name": "A common network of functional areas for attention and eye movements.", - "description": null, - "publication": "Neuron", - "doi": "10.1016/S0896-6273(00)80593-0", - "pmid": "9808463", - "authors": "Corbetta M, Akbudak E, Conturo TE, Snyder AZ, Ollinger JM, Drury HA, Linenweber MR, Petersen SE, Raichle ME, Van Essen DC, Shulman GL", - "year": 1998, - "metadata": null, - "source": "neurosynth", - "source_id": "9808463", - "source_updated_at": null, - "analyses": [ + "id":"3Jvrv4Pct3hb", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "user":null, + "name":"A common network of functional areas for attention and eye movements.", + "description":null, + "publication":"Neuron", + "doi":"10.1016/S0896-6273(00)80593-0", + "pmid":"9808463", + "authors":"Corbetta M, Akbudak E, Conturo TE, Snyder AZ, Ollinger JM, Drury HA, Linenweber MR, Petersen SE, Raichle ME, Van Essen DC, Shulman GL", + "year":1998, + "metadata":null, + "source":"neurosynth", + "source_id":"9808463", + "source_updated_at":null, + "analyses":[ { - "id": "6h7WzZ7sXd7S", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "user": null, - "study": "3Jvrv4Pct3hb", - "name": "35975", - "description": null, - "conditions": [], - "weights": [], - "points": [ + "id":"6h7WzZ7sXd7S", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "user":null, + "study":"3Jvrv4Pct3hb", + "name":"35975", + "description":null, + "conditions":[ + + ], + "weights":[ + + ], + "points":[ { - "id": "7bUZdF3z59wk", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "user": null, - "coordinates": [6.0, -49.0, 10.0], - "analysis": "6h7WzZ7sXd7S", - "kind": "unknown", - "space": "TAL", - "image": null, - "label_id": null, - "entities": [ + "id":"7bUZdF3z59wk", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "user":null, + "coordinates":[ + 6.0, + -49.0, + 10.0 + ], + "analysis":"6h7WzZ7sXd7S", + "kind":"unknown", + "space":"TAL", + "image":null, + "label_id":null, + "entities":[ { - "id": "4BPFBTeFwqWj", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "level": "group", - "label": "35975", - "analysis": "6h7WzZ7sXd7S" + "id":"4BPFBTeFwqWj", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "level":"group", + "label":"35975", + "analysis":"6h7WzZ7sXd7S" } ], - "values": [] + "values":[ + + ] }, { - "id": "4GbFME36aBgD", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "user": null, - "coordinates": [6.0, -67.0, 8.0], - "analysis": "6h7WzZ7sXd7S", - "kind": "unknown", - "space": "TAL", - "image": null, - "label_id": null, - "entities": [ + "id":"4GbFME36aBgD", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "user":null, + "coordinates":[ + 6.0, + -67.0, + 8.0 + ], + "analysis":"6h7WzZ7sXd7S", + "kind":"unknown", + "space":"TAL", + "image":null, + "label_id":null, + "entities":[ { - "id": "6tmukHRo3zZB", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "level": "group", - "label": "35975", - "analysis": "6h7WzZ7sXd7S" + "id":"6tmukHRo3zZB", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "level":"group", + "label":"35975", + "analysis":"6h7WzZ7sXd7S" } ], - "values": [] + "values":[ + + ] }, { - "id": "6RZfQRn4KBa9", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "user": null, - "coordinates": [6.0, -67.0, 8.0], - "analysis": "6h7WzZ7sXd7S", - "kind": "unknown", - "space": "TAL", - "image": null, - "label_id": null, - "entities": [ + "id":"6RZfQRn4KBa9", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "user":null, + "coordinates":[ + 6.0, + -67.0, + 8.0 + ], + "analysis":"6h7WzZ7sXd7S", + "kind":"unknown", + "space":"TAL", + "image":null, + "label_id":null, + "entities":[ { - "id": "3ko2zEd4Adjf", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "level": "group", - "label": "35975", - "analysis": "6h7WzZ7sXd7S" + "id":"3ko2zEd4Adjf", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "level":"group", + "label":"35975", + "analysis":"6h7WzZ7sXd7S" } ], - "values": [] + "values":[ + + ] }, { - "id": "849pEYuZMgtH", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "user": null, - "coordinates": [-37.0, -7.0, 46.0], - "analysis": "6h7WzZ7sXd7S", - "kind": "unknown", - "space": "TAL", - "image": null, - "label_id": null, - "entities": [ + "id":"849pEYuZMgtH", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "user":null, + "coordinates":[ + -37.0, + -7.0, + 46.0 + ], + "analysis":"6h7WzZ7sXd7S", + "kind":"unknown", + "space":"TAL", + "image":null, + "label_id":null, + "entities":[ { - "id": "PmrzkXkgkLn7", - "created_at": "2022-07-22T08:26:40.465069+00:00", - "updated_at": null, - "level": "group", - "label": "35975", - "analysis": "6h7WzZ7sXd7S" + "id":"PmrzkXkgkLn7", + "created_at":"2022-07-22T08:26:40.465069+00:00", + "updated_at":null, + "level":"group", + "label":"35975", + "analysis":"6h7WzZ7sXd7S" } ], - "values": [] + "values":[ + + ] } ], - "images": [] + "images":[ + + ] } ] }, { - "id": "36nHEsLLPwBB", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "name": "Acute Stress Impairs Self-Control in Goal-Directed Choice by Altering Multiple Functional Connections within the Brain’s Decision Circuits", - "description": "Important decisions are often made under stressful\r\ncircumstances thatmight compromise self-regulatory\r\nbehavior. Yet the neural mechanisms by which\r\nstress influences self-control choices are unclear.\r\nWe investigated these mechanisms in human participants\r\nwho faced self-control dilemmas over food\r\nreward while undergoing fMRI following stress.\r\nWe found that stress increased the influence of\r\nimmediately rewarding taste attributes on choice\r\nand reduced self-control. This choice pattern was\r\naccompanied by increased functional connectivity\r\nbetween ventromedial prefrontal cortex (vmPFC)\r\nand amygdala and striatal regions encoding tastiness.\r\nFurthermore, stress was associated with\r\nreduced connectivity between the vmPFC and\r\ndorsolateral prefrontal cortex regions linked to selfcontrol\r\nsuccess. Notably, alterations in connectivity\r\npathways could be dissociated by their differential\r\nrelationships with cortisol and perceived stress.\r\nOur results indicate that stress may compromise\r\nself-control decisions by both enhancing the impact\r\nof immediately rewarding attributes and reducing the\r\nefficacy of regions promoting behaviors that are\r\nconsistent with long-term goals.\r\n\r\nThis collection contains second level correlations of stress induced differences in the influence of taste based decisions on vStr,Amyg and vmPFC and their connectivity.\r\n\r\nKey words: food choice, decision making, self-regulation", - "publication": "Neuron", - "doi": "10.1016/j.neuron.2015.07.005", - "pmid": null, - "authors": "Silvia U. Maier, Aidan B. Makwana and Todd A. Hare", - "year": 1999, - "metadata": { - "url": "https://neurovault.org/collections/3259/", - "download_url": "https://neurovault.org/collections/3259/download", - "owner": 1349, - "contributors": "HareLab", - "owner_name": "silvia.maier", - "number_of_images": 4, - "paper_url": "https://linkinghub.elsevier.com/retrieve/pii/S0896627315006273", - "full_dataset_url": "", - "private": false, - "add_date": "2017-12-12T13:45:50.068678Z", - "modify_date": "2018-11-05T08:22:52.152005Z", - "doi_add_date": "2017-12-12T13:45:50.068157Z", - "type_of_design": "eventrelated", - "number_of_imaging_runs": 3, - "number_of_experimental_units": 70, - "length_of_runs": null, - "length_of_blocks": null, - "length_of_trials": "variable", - "optimization": true, - "optimization_method": "", - "subject_age_mean": 21.15, - "subject_age_min": 18.0, - "subject_age_max": 27.0, - "handedness": "right", - "proportion_male_subjects": 1.0, - "inclusion_exclusion_criteria": "normal or corrected-to-normal vision, non-smokers and refrained from taking any medication for 3 days prior to their scanning session,no history of eating disorders or food allergies and intolerances", - "number_of_rejected_subjects": 3, - "group_comparison": true, - "group_description": "stress induction via socially evaluated cold pressor test vs control group (warm water condition)", - "scanner_make": "Phillips", - "scanner_model": "Philips Achieva 3 T whole-body scanner", - "field_strength": 3.0, - "pulse_sequence": "T2* EPI ", - "parallel_imaging": "SENSE factor 2", - "field_of_view": null, - "matrix_size": null, - "slice_thickness": 3.1, - "skip_distance": 0.6, - "acquisition_orientation": "axial at +15 degree tilt to ACPC", - "order_of_acquisition": "ascending", - "repetition_time": 2460.0, - "echo_time": 30.0, - "flip_angle": 77.0, - "software_package": "", - "software_version": "", - "order_of_preprocessing_operations": "", - "quality_control": "", - "used_b0_unwarping": null, - "b0_unwarping_software": "", - "used_slice_timing_correction": null, - "slice_timing_correction_software": "", - "used_motion_correction": null, - "motion_correction_software": "", - "motion_correction_reference": "", - "motion_correction_metric": "", - "motion_correction_interpolation": "", - "used_motion_susceptibiity_correction": null, - "used_intersubject_registration": null, - "intersubject_registration_software": "", - "intersubject_transformation_type": null, - "nonlinear_transform_type": "", - "transform_similarity_metric": "", - "interpolation_method": "", - "object_image_type": "", - "functional_coregistered_to_structural": null, - "functional_coregistration_method": "", - "coordinate_space": null, - "target_template_image": "", - "target_resolution": null, - "used_smoothing": null, - "smoothing_type": "", - "smoothing_fwhm": null, - "resampled_voxel_size": null, - "intrasubject_model_type": "", - "intrasubject_estimation_type": "", - "intrasubject_modeling_software": "", - "hemodynamic_response_function": "", - "used_temporal_derivatives": null, - "used_dispersion_derivatives": null, - "used_motion_regressors": null, - "used_reaction_time_regressor": null, - "used_orthogonalization": null, - "orthogonalization_description": "", - "used_high_pass_filter": null, - "high_pass_filter_method": "", - "autocorrelation_model": "", - "group_model_type": "", - "group_estimation_type": "", - "group_modeling_software": "", - "group_inference_type": null, - "group_model_multilevel": "", - "group_repeated_measures": null, - "group_repeated_measures_method": "", - "nutbrain_hunger_state": "II", - "nutbrain_food_viewing_conditions": "palatable-healthy, unpalatable-healthy, palatable-unhealthy, unpalatable-unhealthy", - "nutbrain_food_choice_type": "two-alternative forced choice, all combinations of the above foods / image categories possible", - "nutbrain_taste_conditions": "none", - "nutbrain_odor_conditions": "none", - "communities": [2] + "id":"36nHEsLLPwBB", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "name":"Acute Stress Impairs Self-Control in Goal-Directed Choice by Altering Multiple Functional Connections within the Brain’s Decision Circuits", + "description":"Important decisions are often made under stressful\r\ncircumstances thatmight compromise self-regulatory\r\nbehavior. Yet the neural mechanisms by which\r\nstress influences self-control choices are unclear.\r\nWe investigated these mechanisms in human participants\r\nwho faced self-control dilemmas over food\r\nreward while undergoing fMRI following stress.\r\nWe found that stress increased the influence of\r\nimmediately rewarding taste attributes on choice\r\nand reduced self-control. This choice pattern was\r\naccompanied by increased functional connectivity\r\nbetween ventromedial prefrontal cortex (vmPFC)\r\nand amygdala and striatal regions encoding tastiness.\r\nFurthermore, stress was associated with\r\nreduced connectivity between the vmPFC and\r\ndorsolateral prefrontal cortex regions linked to selfcontrol\r\nsuccess. Notably, alterations in connectivity\r\npathways could be dissociated by their differential\r\nrelationships with cortisol and perceived stress.\r\nOur results indicate that stress may compromise\r\nself-control decisions by both enhancing the impact\r\nof immediately rewarding attributes and reducing the\r\nefficacy of regions promoting behaviors that are\r\nconsistent with long-term goals.\r\n\r\nThis collection contains second level correlations of stress induced differences in the influence of taste based decisions on vStr,Amyg and vmPFC and their connectivity.\r\n\r\nKey words: food choice, decision making, self-regulation", + "publication":"Neuron", + "doi":"10.1016/j.neuron.2015.07.005", + "pmid":null, + "authors":"Silvia U. Maier, Aidan B. Makwana and Todd A. Hare", + "year":null, + "metadata":{ + "url":"https://neurovault.org/collections/3259/", + "download_url":"https://neurovault.org/collections/3259/download", + "owner":1349, + "contributors":"HareLab", + "owner_name":"silvia.maier", + "number_of_images":4, + "paper_url":"https://linkinghub.elsevier.com/retrieve/pii/S0896627315006273", + "full_dataset_url":"", + "private":false, + "add_date":"2017-12-12T13:45:50.068678Z", + "modify_date":"2018-11-05T08:22:52.152005Z", + "doi_add_date":"2017-12-12T13:45:50.068157Z", + "type_of_design":"eventrelated", + "number_of_imaging_runs":3, + "number_of_experimental_units":70, + "length_of_runs":null, + "length_of_blocks":null, + "length_of_trials":"variable", + "optimization":true, + "optimization_method":"", + "subject_age_mean":21.15, + "subject_age_min":18.0, + "subject_age_max":27.0, + "handedness":"right", + "proportion_male_subjects":1.0, + "inclusion_exclusion_criteria":"normal or corrected-to-normal vision, non-smokers and refrained from taking any medication for 3 days prior to their scanning session,no history of eating disorders or food allergies and intolerances", + "number_of_rejected_subjects":3, + "group_comparison":true, + "group_description":"stress induction via socially evaluated cold pressor test vs control group (warm water condition)", + "scanner_make":"Phillips", + "scanner_model":"Philips Achieva 3 T whole-body scanner", + "field_strength":3.0, + "pulse_sequence":"T2* EPI ", + "parallel_imaging":"SENSE factor 2", + "field_of_view":null, + "matrix_size":null, + "slice_thickness":3.1, + "skip_distance":0.6, + "acquisition_orientation":"axial at +15 degree tilt to ACPC", + "order_of_acquisition":"ascending", + "repetition_time":2460.0, + "echo_time":30.0, + "flip_angle":77.0, + "software_package":"", + "software_version":"", + "order_of_preprocessing_operations":"", + "quality_control":"", + "used_b0_unwarping":null, + "b0_unwarping_software":"", + "used_slice_timing_correction":null, + "slice_timing_correction_software":"", + "used_motion_correction":null, + "motion_correction_software":"", + "motion_correction_reference":"", + "motion_correction_metric":"", + "motion_correction_interpolation":"", + "used_motion_susceptibiity_correction":null, + "used_intersubject_registration":null, + "intersubject_registration_software":"", + "intersubject_transformation_type":null, + "nonlinear_transform_type":"", + "transform_similarity_metric":"", + "interpolation_method":"", + "object_image_type":"", + "functional_coregistered_to_structural":null, + "functional_coregistration_method":"", + "coordinate_space":null, + "target_template_image":"", + "target_resolution":null, + "used_smoothing":null, + "smoothing_type":"", + "smoothing_fwhm":null, + "resampled_voxel_size":null, + "intrasubject_model_type":"", + "intrasubject_estimation_type":"", + "intrasubject_modeling_software":"", + "hemodynamic_response_function":"", + "used_temporal_derivatives":null, + "used_dispersion_derivatives":null, + "used_motion_regressors":null, + "used_reaction_time_regressor":null, + "used_orthogonalization":null, + "orthogonalization_description":"", + "used_high_pass_filter":null, + "high_pass_filter_method":"", + "autocorrelation_model":"", + "group_model_type":"", + "group_estimation_type":"", + "group_modeling_software":"", + "group_inference_type":null, + "group_model_multilevel":"", + "group_repeated_measures":null, + "group_repeated_measures_method":"", + "nutbrain_hunger_state":"II", + "nutbrain_food_viewing_conditions":"palatable-healthy, unpalatable-healthy, palatable-unhealthy, unpalatable-unhealthy", + "nutbrain_food_choice_type":"two-alternative forced choice, all combinations of the above foods / image categories possible", + "nutbrain_taste_conditions":"none", + "nutbrain_odor_conditions":"none", + "communities":[ + 2 + ] }, - "source": "neurovault", - "source_id": "3259", - "source_updated_at": null, - "analyses": [ + "source":"neurovault", + "source_id":"3259", + "source_updated_at":null, + "analyses":[ { - "id": "5pDk47P9JdU5", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "study": "36nHEsLLPwBB", - "name": "Figure 3. Stress-Induced Differences in the Influence of Taste on Self-Control Choice Behavior and Neural Activity", - "description": "The statistical parametric maps show two regions of the vStr (left) and Amyg (right) where the correlation with relative taste value is higher in the stress compared to control group (p < 0.05 SVC). The color scale represents t statistics derived from 5,000 permutations of the data.", - "conditions": [ + "id":"5pDk47P9JdU5", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "study":"36nHEsLLPwBB", + "name":"Figure 3. Stress-Induced Differences in the Influence of Taste on Self-Control Choice Behavior and Neural Activity", + "description":"The statistical parametric maps show two regions of the vStr (left) and Amyg (right) where the correlation with relative taste value is higher in the stress compared to control group (p < 0.05 SVC). The color scale represents t statistics derived from 5,000 permutations of the data.", + "conditions":[ { - "id": "8EvU75j2xga2", - "user": null, - "name": "None / Other", - "description": null, - "created_at": "2022-07-22T08:27:23.612916+00:00", - "updated_at": null + "id":"8EvU75j2xga2", + "user":null, + "name":"None / Other", + "description":null, + "created_at":"2022-07-22T08:27:23.612916+00:00", + "updated_at":null } ], - "weights": [1.0], - "points": [], - "images": [ + "weights":[ + 1.0 + ], + "points":[ + + ], + "images":[ { - "id": "6M6LMLxB4BLx", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "analysis": "5pDk47P9JdU5", - "analysis_name": "Figure 3. Stress-Induced Differences in the Influence of Taste on Self-Control Choice Behavior and Neural Activity", - "entities": [ + "id":"6M6LMLxB4BLx", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "analysis":"5pDk47P9JdU5", + "analysis_name":"Figure 3. Stress-Induced Differences in the Influence of Taste on Self-Control Choice Behavior and Neural Activity", + "entities":[ { - "id": "6M6LMLxB4BLx", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "level": "group", - "label": "Figure 3. Stress-Induced Differences in the Influence of Taste on Self-Control Choice Behavior and Neural Activity", - "analysis": "5pDk47P9JdU5" + "id":"6M6LMLxB4BLx", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "level":"group", + "label":"Figure 3. Stress-Induced Differences in the Influence of Taste on Self-Control Choice Behavior and Neural Activity", + "analysis":"5pDk47P9JdU5" } ], - "url": "https://neurovault.org/media/images/3259/Tc-Tnc_amy_str_masked_tstat1.nii.gz", - "space": "MNI", - "value_type": "T", - "filename": "Tc-Tnc_amy_str_masked_tstat1.nii.gz", - "add_date": "2017-12-12T13:53:26.590375+00:00" + "url":"https://neurovault.org/media/images/3259/Tc-Tnc_amy_str_masked_tstat1.nii.gz", + "space":"MNI", + "value_type":"T", + "filename":"Tc-Tnc_amy_str_masked_tstat1.nii.gz", + "add_date":"2017-12-12T13:53:26.590375+00:00" } ] }, { - "id": "7tCkPNYJnUfW", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "study": "36nHEsLLPwBB", - "name": "Figure 4. Stress Induction Resulted in Greater Functional Connectivity between the vmPFC and vStr and Amyg when Choosing the Tastier Food", - "description": "The statistical parametric map shows areas of the vStr (upper) and Amyg (lower) where the increase in functional connectivity with vmPFC on trials in which the tastier item was chosen is greater for stress than control participants (p < 0.05 SVC). The color scale represents t statistics derived from 5,000 permutations of the data.", - "conditions": [ + "id":"7tCkPNYJnUfW", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "study":"36nHEsLLPwBB", + "name":"Figure 4. Stress Induction Resulted in Greater Functional Connectivity between the vmPFC and vStr and Amyg when Choosing the Tastier Food", + "description":"The statistical parametric map shows areas of the vStr (upper) and Amyg (lower) where the increase in functional connectivity with vmPFC on trials in which the tastier item was chosen is greater for stress than control participants (p < 0.05 SVC). The color scale represents t statistics derived from 5,000 permutations of the data.", + "conditions":[ { - "id": "8EvU75j2xga2", - "user": null, - "name": "None / Other", - "description": null, - "created_at": "2022-07-22T08:27:23.612916+00:00", - "updated_at": null + "id":"8EvU75j2xga2", + "user":null, + "name":"None / Other", + "description":null, + "created_at":"2022-07-22T08:27:23.612916+00:00", + "updated_at":null } ], - "weights": [1.0], - "points": [], - "images": [ + "weights":[ + 1.0 + ], + "points":[ + + ], + "images":[ { - "id": "5YFCTTYRhdor", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "analysis": "7tCkPNYJnUfW", - "analysis_name": "Figure 4. Stress Induction Resulted in Greater Functional Connectivity between the vmPFC and vStr and Amyg when Choosing the Tastier Food", - "entities": [ + "id":"5YFCTTYRhdor", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "analysis":"7tCkPNYJnUfW", + "analysis_name":"Figure 4. Stress Induction Resulted in Greater Functional Connectivity between the vmPFC and vStr and Amyg when Choosing the Tastier Food", + "entities":[ { - "id": "5YFCTTYRhdor", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "level": "group", - "label": "Figure 4. Stress Induction Resulted in Greater Functional Connectivity between the vmPFC and vStr and Amyg when Choosing the Tastier Food", - "analysis": "7tCkPNYJnUfW" + "id":"5YFCTTYRhdor", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "level":"group", + "label":"Figure 4. Stress Induction Resulted in Greater Functional Connectivity between the vmPFC and vStr and Amyg when Choosing the Tastier Food", + "analysis":"7tCkPNYJnUfW" } ], - "url": "https://neurovault.org/media/images/3259/vmPFC_FV3_nohc_FVc-FVnc_pos_all_tstat1.nii.gz", - "space": "MNI", - "value_type": "T", - "filename": "vmPFC_FV3_nohc_FVc-FVnc_pos_all_tstat1.nii.gz", - "add_date": "2017-12-12T14:01:51.656765+00:00" + "url":"https://neurovault.org/media/images/3259/vmPFC_FV3_nohc_FVc-FVnc_pos_all_tstat1.nii.gz", + "space":"MNI", + "value_type":"T", + "filename":"vmPFC_FV3_nohc_FVc-FVnc_pos_all_tstat1.nii.gz", + "add_date":"2017-12-12T14:01:51.656765+00:00" } ] }, { - "id": "34tFPBsAE2QF", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "study": "36nHEsLLPwBB", - "name": "Figure 5. vmPFC seed region for connectivity analyses", - "description": "Contrast shows BOLD activity for the integrated subjective value of the chosen food", - "conditions": [ + "id":"34tFPBsAE2QF", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "study":"36nHEsLLPwBB", + "name":"Figure 5. vmPFC seed region for connectivity analyses", + "description":"Contrast shows BOLD activity for the integrated subjective value of the chosen food", + "conditions":[ { - "id": "8EvU75j2xga2", - "user": null, - "name": "None / Other", - "description": null, - "created_at": "2022-07-22T08:27:23.612916+00:00", - "updated_at": null + "id":"8EvU75j2xga2", + "user":null, + "name":"None / Other", + "description":null, + "created_at":"2022-07-22T08:27:23.612916+00:00", + "updated_at":null } ], - "weights": [1.0], - "points": [], - "images": [ + "weights":[ + 1.0 + ], + "points":[ + + ], + "images":[ { - "id": "3gh9LPntXL6M", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "analysis": "34tFPBsAE2QF", - "analysis_name": "Figure 5. vmPFC seed region for connectivity analyses", - "entities": [ + "id":"3gh9LPntXL6M", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "analysis":"34tFPBsAE2QF", + "analysis_name":"Figure 5. vmPFC seed region for connectivity analyses", + "entities":[ { - "id": "3gh9LPntXL6M", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "level": "group", - "label": "Figure 5. vmPFC seed region for connectivity analyses", - "analysis": "34tFPBsAE2QF" + "id":"3gh9LPntXL6M", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "level":"group", + "label":"Figure 5. vmPFC seed region for connectivity analyses", + "analysis":"34tFPBsAE2QF" } ], - "url": "https://neurovault.org/media/images/3259/vmPFC_FV3_nohc_FVc_all_tstat1.nii.gz", - "space": "MNI", - "value_type": "T", - "filename": "vmPFC_FV3_nohc_FVc_all_tstat1.nii.gz", - "add_date": "2017-12-12T15:10:48.317190+00:00" + "url":"https://neurovault.org/media/images/3259/vmPFC_FV3_nohc_FVc_all_tstat1.nii.gz", + "space":"MNI", + "value_type":"T", + "filename":"vmPFC_FV3_nohc_FVc_all_tstat1.nii.gz", + "add_date":"2017-12-12T15:10:48.317190+00:00" } ] }, { - "id": "6AdMa3eLernw", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "study": "36nHEsLLPwBB", - "name": "supplemental to Figure 5. vmPFC seed region for connectivity analyses", - "description": "BOLD contrast shows the integrated subjective relative food value (value of the chosen food - value of the non-chosen food).", - "conditions": [ + "id":"6AdMa3eLernw", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "study":"36nHEsLLPwBB", + "name":"supplemental to Figure 5. vmPFC seed region for connectivity analyses", + "description":"BOLD contrast shows the integrated subjective relative food value (value of the chosen food - value of the non-chosen food).", + "conditions":[ { - "id": "8EvU75j2xga2", - "user": null, - "name": "None / Other", - "description": null, - "created_at": "2022-07-22T08:27:23.612916+00:00", - "updated_at": null + "id":"8EvU75j2xga2", + "user":null, + "name":"None / Other", + "description":null, + "created_at":"2022-07-22T08:27:23.612916+00:00", + "updated_at":null } ], - "weights": [1.0], - "points": [], - "images": [ + "weights":[ + 1.0 + ], + "points":[ + + ], + "images":[ { - "id": "cnuvMAVybD7v", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "user": null, - "analysis": "6AdMa3eLernw", - "analysis_name": "supplemental to Figure 5. vmPFC seed region for connectivity analyses", - "entities": [ + "id":"cnuvMAVybD7v", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "user":null, + "analysis":"6AdMa3eLernw", + "analysis_name":"supplemental to Figure 5. vmPFC seed region for connectivity analyses", + "entities":[ { - "id": "cnuvMAVybD7v", - "created_at": "2022-07-22T08:29:51.625947+00:00", - "updated_at": null, - "level": "group", - "label": "supplemental to Figure 5. vmPFC seed region for connectivity analyses", - "analysis": "6AdMa3eLernw" + "id":"cnuvMAVybD7v", + "created_at":"2022-07-22T08:29:51.625947+00:00", + "updated_at":null, + "level":"group", + "label":"supplemental to Figure 5. vmPFC seed region for connectivity analyses", + "analysis":"6AdMa3eLernw" } ], - "url": "https://neurovault.org/media/images/3259/vmPFC_FV3_nohc_FVc-FVnc_pos_all_tstat1_1.nii.gz", - "space": "MNI", - "value_type": "T", - "filename": "vmPFC_FV3_nohc_FVc-FVnc_pos_all_tstat1_1.nii.gz", - "add_date": "2017-12-12T15:12:07.649027+00:00" + "url":"https://neurovault.org/media/images/3259/vmPFC_FV3_nohc_FVc-FVnc_pos_all_tstat1_1.nii.gz", + "space":"MNI", + "value_type":"T", + "filename":"vmPFC_FV3_nohc_FVc-FVnc_pos_all_tstat1_1.nii.gz", + "add_date":"2017-12-12T15:12:07.649027+00:00" } ] } ] }, { - "id": "3zutS8kyg2sy", - "created_at": "2022-07-22T08:31:05.214847+00:00", - "updated_at": null, - "user": null, - "name": "Activation in Context: Differential Conclusions Drawn from Cross-Sectional and Longitudinal Analyses of Adolescents’ Cognitive Control-Related Neural Activity", - "description": "", - "publication": "Frontiers in Human Neuroscience", - "doi": "10.3389/fnhum.2017.00141", - "pmid": "26247866", - "authors": "Ethan M. McCormick, Yang Qu and Eva H. Telzer", - "year": 2000, - "metadata": { - "url": "https://neurovault.org/collections/2411/", - "download_url": "https://neurovault.org/collections/2411/download", - "owner": 1274, - "contributors": "ehtelzer", - "owner_name": "emccormick20", - "number_of_images": 1, - "paper_url": "http://journal.frontiersin.org/article/10.3389/fnhum.2017.00141/full", - "full_dataset_url": "", - "private": false, - "add_date": "2017-04-04T17:47:16.863092Z", - "modify_date": "2019-11-10T20:39:12.916636Z", - "doi_add_date": "2019-11-10T20:39:12.911623Z", - "type_of_design": null, - "number_of_imaging_runs": null, - "number_of_experimental_units": null, - "length_of_runs": null, - "length_of_blocks": null, - "length_of_trials": "", - "optimization": null, - "optimization_method": "", - "subject_age_mean": null, - "subject_age_min": null, - "subject_age_max": null, - "handedness": null, - "proportion_male_subjects": null, - "inclusion_exclusion_criteria": "", - "number_of_rejected_subjects": null, - "group_comparison": null, - "group_description": "", - "scanner_make": "", - "scanner_model": "", - "field_strength": null, - "pulse_sequence": "", - "parallel_imaging": "", - "field_of_view": null, - "matrix_size": null, - "slice_thickness": null, - "skip_distance": null, - "acquisition_orientation": "", - "order_of_acquisition": null, - "repetition_time": null, - "echo_time": null, - "flip_angle": null, - "software_package": "", - "software_version": "", - "order_of_preprocessing_operations": "", - "quality_control": "", - "used_b0_unwarping": null, - "b0_unwarping_software": "", - "used_slice_timing_correction": null, - "slice_timing_correction_software": "", - "used_motion_correction": null, - "motion_correction_software": "", - "motion_correction_reference": "", - "motion_correction_metric": "", - "motion_correction_interpolation": "", - "used_motion_susceptibiity_correction": null, - "used_intersubject_registration": null, - "intersubject_registration_software": "", - "intersubject_transformation_type": null, - "nonlinear_transform_type": "", - "transform_similarity_metric": "", - "interpolation_method": "", - "object_image_type": "", - "functional_coregistered_to_structural": null, - "functional_coregistration_method": "", - "coordinate_space": null, - "target_template_image": "", - "target_resolution": null, - "used_smoothing": null, - "smoothing_type": "", - "smoothing_fwhm": null, - "resampled_voxel_size": null, - "intrasubject_model_type": "", - "intrasubject_estimation_type": "", - "intrasubject_modeling_software": "", - "hemodynamic_response_function": "", - "used_temporal_derivatives": null, - "used_dispersion_derivatives": null, - "used_motion_regressors": null, - "used_reaction_time_regressor": null, - "used_orthogonalization": null, - "orthogonalization_description": "", - "used_high_pass_filter": null, - "high_pass_filter_method": "", - "autocorrelation_model": "", - "group_model_type": "", - "group_estimation_type": "", - "group_modeling_software": "", - "group_inference_type": null, - "group_model_multilevel": "", - "group_repeated_measures": null, - "group_repeated_measures_method": "", - "nutbrain_hunger_state": null, - "nutbrain_food_viewing_conditions": "", - "nutbrain_food_choice_type": "", - "nutbrain_taste_conditions": "", - "nutbrain_odor_conditions": "", - "communities": [] + "id":"3zutS8kyg2sy", + "created_at":"2022-07-22T08:31:05.214847+00:00", + "updated_at":null, + "user":null, + "name":"Activation in Context: Differential Conclusions Drawn from Cross-Sectional and Longitudinal Analyses of Adolescents’ Cognitive Control-Related Neural Activity", + "description":"", + "publication":"Frontiers in Human Neuroscience", + "doi":"10.3389/fnhum.2017.00141", + "pmid":null, + "authors":"Ethan M. McCormick, Yang Qu and Eva H. Telzer", + "year":null, + "metadata":{ + "url":"https://neurovault.org/collections/2411/", + "download_url":"https://neurovault.org/collections/2411/download", + "owner":1274, + "contributors":"ehtelzer", + "owner_name":"emccormick20", + "number_of_images":1, + "paper_url":"http://journal.frontiersin.org/article/10.3389/fnhum.2017.00141/full", + "full_dataset_url":"", + "private":false, + "add_date":"2017-04-04T17:47:16.863092Z", + "modify_date":"2019-11-10T20:39:12.916636Z", + "doi_add_date":"2019-11-10T20:39:12.911623Z", + "type_of_design":null, + "number_of_imaging_runs":null, + "number_of_experimental_units":null, + "length_of_runs":null, + "length_of_blocks":null, + "length_of_trials":"", + "optimization":null, + "optimization_method":"", + "subject_age_mean":null, + "subject_age_min":null, + "subject_age_max":null, + "handedness":null, + "proportion_male_subjects":null, + "inclusion_exclusion_criteria":"", + "number_of_rejected_subjects":null, + "group_comparison":null, + "group_description":"", + "scanner_make":"", + "scanner_model":"", + "field_strength":null, + "pulse_sequence":"", + "parallel_imaging":"", + "field_of_view":null, + "matrix_size":null, + "slice_thickness":null, + "skip_distance":null, + "acquisition_orientation":"", + "order_of_acquisition":null, + "repetition_time":null, + "echo_time":null, + "flip_angle":null, + "software_package":"", + "software_version":"", + "order_of_preprocessing_operations":"", + "quality_control":"", + "used_b0_unwarping":null, + "b0_unwarping_software":"", + "used_slice_timing_correction":null, + "slice_timing_correction_software":"", + "used_motion_correction":null, + "motion_correction_software":"", + "motion_correction_reference":"", + "motion_correction_metric":"", + "motion_correction_interpolation":"", + "used_motion_susceptibiity_correction":null, + "used_intersubject_registration":null, + "intersubject_registration_software":"", + "intersubject_transformation_type":null, + "nonlinear_transform_type":"", + "transform_similarity_metric":"", + "interpolation_method":"", + "object_image_type":"", + "functional_coregistered_to_structural":null, + "functional_coregistration_method":"", + "coordinate_space":null, + "target_template_image":"", + "target_resolution":null, + "used_smoothing":null, + "smoothing_type":"", + "smoothing_fwhm":null, + "resampled_voxel_size":null, + "intrasubject_model_type":"", + "intrasubject_estimation_type":"", + "intrasubject_modeling_software":"", + "hemodynamic_response_function":"", + "used_temporal_derivatives":null, + "used_dispersion_derivatives":null, + "used_motion_regressors":null, + "used_reaction_time_regressor":null, + "used_orthogonalization":null, + "orthogonalization_description":"", + "used_high_pass_filter":null, + "high_pass_filter_method":"", + "autocorrelation_model":"", + "group_model_type":"", + "group_estimation_type":"", + "group_modeling_software":"", + "group_inference_type":null, + "group_model_multilevel":"", + "group_repeated_measures":null, + "group_repeated_measures_method":"", + "nutbrain_hunger_state":null, + "nutbrain_food_viewing_conditions":"", + "nutbrain_food_choice_type":"", + "nutbrain_taste_conditions":"", + "nutbrain_odor_conditions":"", + "communities":[ + + ] }, - "source": "neurovault", - "source_id": "2411", - "source_updated_at": null, - "analyses": [ + "source":"neurovault", + "source_id":"2411", + "source_updated_at":null, + "analyses":[ { - "id": "5Z95x7T3TAQW", - "created_at": "2022-07-22T08:31:05.214847+00:00", - "updated_at": null, - "user": null, - "study": "3zutS8kyg2sy", - "name": "Wave 1 Nogo Trials", - "description": "", - "conditions": [ + "id":"5Z95x7T3TAQW", + "created_at":"2022-07-22T08:31:05.214847+00:00", + "updated_at":null, + "user":null, + "study":"3zutS8kyg2sy", + "name":"Wave 1 Nogo Trials", + "description":"", + "conditions":[ { - "id": "bSTJudKMNuNb", - "user": null, - "name": "go/no-go task", - "description": null, - "created_at": "2022-07-22T08:29:45.755542+00:00", - "updated_at": null + "id":"bSTJudKMNuNb", + "user":null, + "name":"go/no-go task", + "description":null, + "created_at":"2022-07-22T08:29:45.755542+00:00", + "updated_at":null } ], - "weights": [1.0], - "points": [], - "images": [ + "weights":[ + 1.0 + ], + "points":[ + + ], + "images":[ { - "id": "6jD4rqyV9sC6", - "created_at": "2022-07-22T08:31:05.214847+00:00", - "updated_at": null, - "user": null, - "analysis": "5Z95x7T3TAQW", - "analysis_name": "Wave 1 Nogo Trials", - "entities": [ + "id":"6jD4rqyV9sC6", + "created_at":"2022-07-22T08:31:05.214847+00:00", + "updated_at":null, + "user":null, + "analysis":"5Z95x7T3TAQW", + "analysis_name":"Wave 1 Nogo Trials", + "entities":[ { - "id": "6jD4rqyV9sC6", - "created_at": "2022-07-22T08:31:05.214847+00:00", - "updated_at": null, - "level": "group", - "label": "Wave 1 Nogo Trials", - "analysis": "5Z95x7T3TAQW" + "id":"6jD4rqyV9sC6", + "created_at":"2022-07-22T08:31:05.214847+00:00", + "updated_at":null, + "level":"group", + "label":"Wave 1 Nogo Trials", + "analysis":"5Z95x7T3TAQW" } ], - "url": "https://neurovault.org/media/images/2411/0001_T_Nogo_PM_T1_pos.nii.gz", - "space": "MNI", - "value_type": "T", - "filename": "0001_T_Nogo_PM_T1_pos.nii.gz", - "add_date": "2017-04-04T17:50:17.966806+00:00" + "url":"https://neurovault.org/media/images/2411/0001_T_Nogo_PM_T1_pos.nii.gz", + "space":"MNI", + "value_type":"T", + "filename":"0001_T_Nogo_PM_T1_pos.nii.gz", + "add_date":"2017-04-04T17:50:17.966806+00:00" } ] } ] } ] -} +} \ No newline at end of file diff --git a/compose/neurosynth-frontend/package-lock.json b/compose/neurosynth-frontend/package-lock.json index 601442bd..c5b527a4 100644 --- a/compose/neurosynth-frontend/package-lock.json +++ b/compose/neurosynth-frontend/package-lock.json @@ -26,7 +26,6 @@ "@mui/x-data-grid": "^5.10.0", "@reactour/tour": "^2.10.3", "@sentry/react": "^7.48.0", - "@tanstack/react-table": "^8.20.5", "@types/jest": "^26.0.24", "@types/react": "^17.0.13", "@types/react-dom": "^17.0.8", @@ -4964,37 +4963,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@tanstack/react-table": { - "version": "8.20.5", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", - "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", - "dependencies": { - "@tanstack/table-core": "8.20.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/@tanstack/table-core": { - "version": "8.20.5", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", - "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, "node_modules/@testing-library/dom": { "version": "8.20.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", @@ -28708,19 +28676,6 @@ "tslib": "^2.4.0" } }, - "@tanstack/react-table": { - "version": "8.20.5", - "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", - "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", - "requires": { - "@tanstack/table-core": "8.20.5" - } - }, - "@tanstack/table-core": { - "version": "8.20.5", - "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", - "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==" - }, "@testing-library/dom": { "version": "8.20.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", diff --git a/compose/neurosynth-frontend/package.json b/compose/neurosynth-frontend/package.json index 6a105f7e..178592a2 100644 --- a/compose/neurosynth-frontend/package.json +++ b/compose/neurosynth-frontend/package.json @@ -21,7 +21,6 @@ "@mui/x-data-grid": "^5.10.0", "@reactour/tour": "^2.10.3", "@sentry/react": "^7.48.0", - "@tanstack/react-table": "^8.20.5", "@types/jest": "^26.0.24", "@types/react": "^17.0.13", "@types/react-dom": "^17.0.8", diff --git a/compose/neurosynth-frontend/src/components/DebouncedTextField.tsx b/compose/neurosynth-frontend/src/components/DebouncedTextField.tsx deleted file mode 100644 index 45313e56..00000000 --- a/compose/neurosynth-frontend/src/components/DebouncedTextField.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { TextField, TextFieldProps } from '@mui/material'; -import { ChangeEvent, useEffect, useState } from 'react'; - -type EDebouncedTextFieldProps = Omit & { - onChange?: (value: string | undefined) => void; - value?: string | undefined; -}; - -const DebouncedTextField: React.FC = ({ - value, - onChange, - ...otherProps -}) => { - const [debouncedValue, setDebouncedValue] = useState(value || ''); - - useEffect(() => { - const debounce = setTimeout(() => { - onChange && onChange(debouncedValue); - }, 400); - return () => { - clearTimeout(debounce); - }; - }, [debouncedValue, onChange]); - - // when an update occurs from outside the component, we want to reflect that new value (like if a filter is cleard) - useEffect(() => { - setDebouncedValue(value || ''); - }, [value]); - - const handleOnChange = (event: ChangeEvent) => { - setDebouncedValue(event.target.value); - }; - - return ; -}; - -export default DebouncedTextField; diff --git a/compose/neurosynth-frontend/src/components/Dialogs/ConfirmationDialog.tsx b/compose/neurosynth-frontend/src/components/Dialogs/ConfirmationDialog.tsx index 3fa9b23a..aa1b9811 100644 --- a/compose/neurosynth-frontend/src/components/Dialogs/ConfirmationDialog.tsx +++ b/compose/neurosynth-frontend/src/components/Dialogs/ConfirmationDialog.tsx @@ -59,8 +59,7 @@ const ConfirmationDialog: React.FC = (props) => { sx={{ width: '250px' }} onClick={() => props.onCloseDialog(true, props.data)} variant="contained" - color="primary" - disableElevation + color="success" > {props.confirmText ? props.confirmText : 'Confirm'} diff --git a/compose/neurosynth-frontend/src/components/Search/SearchContainer.tsx b/compose/neurosynth-frontend/src/components/Search/SearchContainer.tsx index 8f7f1dc6..f9108fe8 100644 --- a/compose/neurosynth-frontend/src/components/Search/SearchContainer.tsx +++ b/compose/neurosynth-frontend/src/components/Search/SearchContainer.tsx @@ -18,7 +18,7 @@ export interface ISearchContainer { searchMode?: 'study-search' | 'project-search'; } -export const getNumTotalPages = (totalCount: number | undefined, pageSize: number | undefined) => { +const getNumTotalPages = (totalCount: number | undefined, pageSize: number | undefined) => { if (!totalCount || !pageSize) { return 0; } diff --git a/compose/neurosynth-frontend/src/pages/Extraction/ExtractionPage.tsx b/compose/neurosynth-frontend/src/pages/Extraction/ExtractionPage.tsx index 395d3593..10901dfd 100644 --- a/compose/neurosynth-frontend/src/pages/Extraction/ExtractionPage.tsx +++ b/compose/neurosynth-frontend/src/pages/Extraction/ExtractionPage.tsx @@ -1,19 +1,25 @@ -import { Box, Button, Typography } from '@mui/material'; +import BookmarkIcon from '@mui/icons-material/Bookmark'; +import CheckIcon from '@mui/icons-material/Check'; +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import { Box, Button, Chip, Typography } from '@mui/material'; import NeurosynthBreadcrumbs from 'components/NeurosynthBreadcrumbs'; import ProjectIsLoadingText from 'components/ProjectIsLoadingText'; import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; import TextEdit from 'components/TextEdit/TextEdit'; import { useGetStudysetById, useUpdateStudyset } from 'hooks'; import useGetExtractionSummary from 'hooks/useGetExtractionSummary'; +import useGetWindowHeight from 'hooks/useGetWindowHeight'; import useUserCanEdit from 'hooks/useUserCanEdit'; import { StudyReturn } from 'neurostore-typescript-sdk'; import ExtractionOutOfSync from 'pages/Extraction/components/ExtractionOutOfSync'; +import ReadOnlyStudySummaryVirtualizedItem from 'pages/Extraction/components/ReadOnlyStudySummary'; import { resolveStudysetAndCurationDifferences } from 'pages/Extraction/Extraction.helpers'; import { IProjectPageLocationState } from 'pages/Project/ProjectPage'; import { useGetProjectIsLoading, useInitProjectStoreIfRequired, useProjectCurationColumns, + useProjectExtractionStudyStatusList, useProjectExtractionStudysetId, useProjectMetaAnalysisCanEdit, useProjectName, @@ -21,7 +27,7 @@ import { } from 'pages/Project/store/ProjectStore'; import { useEffect, useMemo, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; -import ExtractionTable from './components/ExtractionTable'; +import { FixedSizeList, ListChildComponentProps } from 'react-window'; export enum EExtractionStatus { 'COMPLETED' = 'completed', @@ -29,14 +35,37 @@ export enum EExtractionStatus { 'UNCATEGORIZED' = 'uncategorized', } +const ReadOnlyStudySummaryFixedSizeListRow: React.FC< + ListChildComponentProps<{ + studies: StudyReturn[]; + currentSelectedChip: EExtractionStatus; + canEdit: boolean; + }> +> = (props) => { + const study = props.data.studies[props.index]; + const currentSelectedChip = props.data.currentSelectedChip; + const canEdit = props.data.canEdit; + + return ( + + ); +}; + const ExtractionPage: React.FC = (props) => { const { projectId } = useParams<{ projectId: string | undefined }>(); const navigate = useNavigate(); + const windowHeight = useGetWindowHeight(); useInitProjectStoreIfRequired(); const projectName = useProjectName(); const studysetId = useProjectExtractionStudysetId(); + const studyStatusList = useProjectExtractionStudyStatusList(); const columns = useProjectCurationColumns(); const loading = useGetProjectIsLoading(); const extractionSummary = useGetExtractionSummary(projectId || ''); @@ -53,6 +82,20 @@ const ExtractionPage: React.FC = (props) => { const { mutate } = useUpdateStudyset(); const [fieldBeingUpdated, setFieldBeingUpdated] = useState(''); + const selectedChipLocalStorageKey = `SELECTED_CHIP-${projectId}`; + const selectedChipInLocalStorage = + (localStorage.getItem(selectedChipLocalStorageKey) as EExtractionStatus) || + EExtractionStatus.UNCATEGORIZED; + const [currentChip, setCurrentChip] = useState(selectedChipInLocalStorage); + const [studiesDisplayedState, setStudiesDisplayedState] = useState<{ + uncategorized: StudyReturn[]; + saveForLater: StudyReturn[]; + completed: StudyReturn[]; + }>({ + uncategorized: [], + saveForLater: [], + completed: [], + }); const [showReconcilePrompt, setShowReconcilePrompt] = useState(false); useEffect(() => { @@ -66,6 +109,52 @@ const ExtractionPage: React.FC = (props) => { } }, [columns, getStudysetIsLoading, studyset?.studies, loading]); + useEffect(() => { + if (studyStatusList && studyset?.studies) { + const map = new Map(); + + studyStatusList.forEach((studyStatus) => { + map.set(studyStatus.id, studyStatus.status); + }); + + setStudiesDisplayedState((prev) => { + if (!prev) return prev; + + const allStudies = studyset.studies as StudyReturn[]; + + const completed: StudyReturn[] = []; + const saveForLater: StudyReturn[] = []; + const uncategorized: StudyReturn[] = []; + + allStudies.forEach((study) => { + if (!study?.id) return; + + if (map.has(study.id)) { + const status = map.get(study.id); + status === EExtractionStatus.COMPLETED + ? completed.push(study) + : saveForLater.push(study); + } else { + uncategorized.push(study); + } + }); + + return { + completed, + saveForLater, + uncategorized, + }; + }); + } + }, [studyStatusList, studyset?.studies]); + + const handleSelectChip = (arg: EExtractionStatus) => { + if (projectId) { + setCurrentChip(arg); + localStorage.setItem(selectedChipLocalStorageKey, arg); + } + }; + const handleUpdateStudyset = (updatedText: string, fieldName: string) => { if (studysetId) { setFieldBeingUpdated(fieldName); @@ -99,6 +188,22 @@ const ExtractionPage: React.FC = (props) => { } }; + const studiesDisplayed = + currentChip === EExtractionStatus.COMPLETED + ? studiesDisplayedState.completed + : currentChip === EExtractionStatus.SAVEDFORLATER + ? studiesDisplayedState.saveForLater + : studiesDisplayedState.uncategorized; + + const text = + currentChip === EExtractionStatus.COMPLETED + ? 'completed' + : currentChip === EExtractionStatus.SAVEDFORLATER + ? 'saved for later' + : 'uncategorized'; + + const pxInVh = useMemo(() => Math.round((windowHeight * 60) / 100), [windowHeight]); + const isReadyToMoveToNextStep = useMemo( () => extractionSummary.total === extractionSummary.completed && extractionSummary.total > 0, @@ -202,9 +307,80 @@ const ExtractionPage: React.FC = (props) => { - - - + + + handleSelectChip(EExtractionStatus.UNCATEGORIZED)} + color="warning" + sx={{ marginRight: '8px' }} + variant={ + currentChip === EExtractionStatus.UNCATEGORIZED + ? 'filled' + : 'outlined' + } + icon={} + label={`Uncategorized (${studiesDisplayedState.uncategorized.length})`} + /> + handleSelectChip(EExtractionStatus.SAVEDFORLATER)} + variant={ + currentChip === EExtractionStatus.SAVEDFORLATER + ? 'filled' + : 'outlined' + } + color="info" + sx={{ marginRight: '8px' }} + icon={} + label={`Save for later (${studiesDisplayedState.saveForLater.length})`} + /> + handleSelectChip(EExtractionStatus.COMPLETED)} + variant={ + currentChip === EExtractionStatus.COMPLETED ? 'filled' : 'outlined' + } + color="success" + sx={{ marginRight: '8px' }} + icon={} + label={`Completed (${studiesDisplayedState.completed.length})`} + /> + + + + {studiesDisplayed.length} studies + + + + + {studiesDisplayed.length === 0 && ( + + No studies marked as {text} + + )} + + data.studies[index]?.id || index} + itemData={{ + studies: studiesDisplayed, + currentSelectedChip: currentChip, + canEdit: canEdit, + }} + layout="vertical" + overscanCount={3} + > + {ReadOnlyStudySummaryFixedSizeListRow} + + diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.module.css b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.module.css deleted file mode 100644 index 7a6e3849..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.module.css +++ /dev/null @@ -1,12 +0,0 @@ -.completed { - background-color: #e5ffe5; -} - -.savedforlater { - background-color: #effbff; -} - -.uncategorized { - /* background-color: #ffffeb; */ - background-color: white; -} \ No newline at end of file diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.tsx deleted file mode 100644 index f61f96cd..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.tsx +++ /dev/null @@ -1,429 +0,0 @@ -import { - Box, - Chip, - Pagination, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TablePagination, - TableRow, - Typography, -} from '@mui/material'; -import { - ColumnFiltersState, - createColumnHelper, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - PaginationState, - RowData, - SortingState, - useReactTable, -} from '@tanstack/react-table'; -import { useGetStudysetById } from 'hooks'; -import { IStudyExtractionStatus } from 'hooks/projects/useGetProjects'; -import { StudyReturn } from 'neurostore-typescript-sdk'; -import { - useProjectExtractionStudysetId, - useProjectExtractionStudyStatusList, - useProjectId, -} from 'pages/Project/store/ProjectStore'; -import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { EExtractionStatus } from '../ExtractionPage'; -import styles from './ExtractionTable.module.css'; -import { ExtractionTableAuthorCell, ExtractionTableAuthorHeader } from './ExtractionTableAuthor'; -import { ExtractionTableDOICell, ExtractionTableDOIHeader } from './ExtractionTableDOI'; -import ExtractionTableFilterInput from './ExtractionTableFilterInput'; -import { ExtractionTableJournalCell, ExtractionTableJournalHeader } from './ExtractionTableJournal'; -import { ExtractionTableNameCell, ExtractionTableNameHeader } from './ExtractionTableName'; -import { ExtractionTablePMIDCell, ExtractionTablePMIDHeader } from './ExtractionTablePMID'; -import { ExtractionTableStatusCell, ExtractionTableStatusHeader } from './ExtractionTableStatus'; -import { ExtractionTableYearCell, ExtractionTableYearHeader } from './ExtractionTableYear'; - -//allows us to define custom properties for our columns -declare module '@tanstack/react-table' { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - interface ColumnMeta { - filterVariant?: 'text' | 'status-select' | 'journal-autocomplete'; - } -} - -export type IExtractionTableStudy = StudyReturn & { status: EExtractionStatus | undefined }; - -const columnHelper = createColumnHelper(); - -const ExtractionTable: React.FC = () => { - const studysetId = useProjectExtractionStudysetId(); - const projectId = useProjectId(); - const navigate = useNavigate(); - const studyStatusList = useProjectExtractionStudyStatusList(); - const { data: studyset } = useGetStudysetById(studysetId, true); // this should already be loaded in the cache from the parent component - - const [pagination, setPagination] = useState({ - pageIndex: 0, - pageSize: 25, - }); - - useEffect(() => { - const state = sessionStorage.getItem(`${projectId}-extraction-table`); - if (!state) return; - - const parsedState = JSON.parse(state) as { - columnFilters: ColumnFiltersState; - sorting: SortingState; - studies: string[]; - }; - - if (parsedState.columnFilters) setColumnFilters(parsedState.columnFilters); - if (parsedState.sorting) setSorting(parsedState.sorting); - }, [projectId]); - - const [columnFilters, setColumnFilters] = useState([]); - const [sorting, setSorting] = useState([]); - - const studyStatusMap = useMemo(() => { - const map = new Map(); - studyStatusList?.forEach((studyStatus) => { - map.set(studyStatus.id, studyStatus); - }); - return map; - }, [studyStatusList]); - - const data: Array = useMemo(() => { - const studies = (studyset?.studies || []) as Array; - return studies.map((study) => ({ - ...study, - status: studyStatusMap.get(study?.id || '')?.status, - })); - }, [studyStatusMap, studyset?.studies]); - - const columns = useMemo(() => { - return [ - columnHelper.accessor(({ year }) => (year ? String(year) : ''), { - id: 'year', - size: 5, - minSize: 5, - maxSize: 5, - cell: ExtractionTableYearCell, - header: ExtractionTableYearHeader, - enableSorting: true, - enableColumnFilter: true, - filterFn: 'includesString', - meta: { - filterVariant: 'text', - }, - }), - columnHelper.accessor('name', { - id: 'name', - cell: ExtractionTableNameCell, - size: 25, - minSize: 25, - maxSize: 25, - header: ExtractionTableNameHeader, - enableSorting: true, - sortingFn: 'text', - filterFn: 'includesString', - meta: { - filterVariant: 'text', - }, - }), - columnHelper.accessor('authors', { - id: 'authors', - size: 20, - minSize: 20, - maxSize: 20, - enableSorting: true, - enableColumnFilter: true, - sortingFn: 'text', - filterFn: 'includesString', - cell: ExtractionTableAuthorCell, - header: ExtractionTableAuthorHeader, - meta: { - filterVariant: 'text', - }, - }), - columnHelper.accessor('publication', { - id: 'journal', - size: 15, - minSize: 15, - maxSize: 15, - enableSorting: true, - enableColumnFilter: true, - cell: ExtractionTableJournalCell, - header: ExtractionTableJournalHeader, - meta: { - filterVariant: 'journal-autocomplete', - }, - }), - columnHelper.accessor('doi', { - id: 'doi', - size: 15, - minSize: 15, - maxSize: 15, - sortingFn: 'alphanumeric', - enableSorting: true, - enableColumnFilter: true, - filterFn: 'includesString', - cell: ExtractionTableDOICell, - header: ExtractionTableDOIHeader, - meta: { - filterVariant: 'text', - }, - }), - columnHelper.accessor('pmid', { - id: 'pmid', - size: 10, - minSize: 10, - maxSize: 10, - enableColumnFilter: true, - filterFn: 'includesString', - cell: ExtractionTablePMIDCell, - header: ExtractionTablePMIDHeader, - enableSorting: true, - sortingFn: 'alphanumeric', - meta: { - filterVariant: 'text', - }, - }), - columnHelper.accessor('status', { - id: 'status', - size: 10, - minSize: 10, - maxSize: 10, - enableSorting: true, - cell: ExtractionTableStatusCell, - filterFn: (row, columnId, filterValue: EExtractionStatus | null) => { - if (filterValue === null) return true; - const studyStatus = row.getValue(columnId) as EExtractionStatus | undefined; - - // uncategorized can be undefined or it can be "uncategorized" - if (filterValue === EExtractionStatus.UNCATEGORIZED) { - return studyStatus === filterValue || studyStatus === undefined; - } - - return studyStatus === filterValue; - }, - header: ExtractionTableStatusHeader, - enableColumnFilter: true, - meta: { - filterVariant: 'status-select', - }, - }), - ]; - }, []); - - const table = useReactTable({ - data: data, - columns: columns, - onSortingChange: setSorting, - onPaginationChange: setPagination, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - onColumnFiltersChange: setColumnFilters, - state: { - pagination: pagination, - columnFilters: columnFilters, - sorting: sorting, - }, - meta: { - studyStatusMap, - }, - }); - - const handleRowsPerPageChange = useCallback( - (event: ChangeEvent) => { - const newRowsPerPage = parseInt(event.target.value); - if (!isNaN(newRowsPerPage)) setPagination({ pageIndex: 0, pageSize: newRowsPerPage }); - }, - [] - ); - - const handlePaginationChange = useCallback((_event: any, page: number) => { - // page is 0 indexed - setPagination((prev) => ({ - ...prev, - pageIndex: page, - })); - }, []); - - // the two pagination functionds act differently so we need to assume different things for each - const handlePaginationChangeMuiPaginator = useCallback((_event: any, page: number) => { - // page is 0 indexed - setPagination((prev) => ({ - ...prev, - pageIndex: page - 1, - })); - }, []); - - return ( - - - - Total: {data.length} studies - - - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {header.column.getCanFilter() ? ( - - ) : ( - - )} - - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - { - if (!row.original.id) return; - sessionStorage.setItem( - `${projectId}-extraction-table`, - JSON.stringify({ - columnFilters: table.getState().columnFilters, - sorting: table.getState().sorting, - studies: table - .getSortedRowModel() - .rows.map((r) => r.original.id), - }) - ); - navigate( - `/projects/${projectId}/extraction/studies/${row.original.id}/edit` - ); - }} - sx={{ - '&:hover': { filter: 'brightness(0.9)', cursor: 'pointer' }, - }} - > - {row.getVisibleCells().map((cell) => ( - - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - - ))} - - ))} - -
-
- - - - - {columnFilters - .filter((filter) => !!filter.value) - .map((filter) => ( - - table.setColumnFilters((prev) => - prev.filter((f) => f.id !== filter.id) - ) - } - key={filter.id} - color="primary" - variant="outlined" - sx={{ margin: '1px', fontSize: '12px', maxWidth: '200px' }} - label={`Filtering ${filter.id.toUpperCase()}: ${filter.value}`} - size="small" - /> - ))} - {sorting.map((sort) => ( - { - table.setSorting((prev) => - prev.filter((f) => f.id !== sort.id) - ); - }} - color="secondary" - variant="outlined" - sx={{ margin: '1px', fontSize: '12px', maxWidth: '200px' }} - label={`Sorting by ${sort.id.toUpperCase()}: ${ - sort.desc ? 'desc' : 'asc' - }`} - size="small" - /> - ))} - - - - Viewing {table.getFilteredRowModel().rows.length} / {data.length} - - - - -
- ); -}; - -export default ExtractionTable; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableAuthor.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableAuthor.tsx deleted file mode 100644 index 117ab4ed..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableAuthor.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Box, IconButton, Link, Tooltip, Typography } from '@mui/material'; -import { CellContext, HeaderContext } from '@tanstack/react-table'; -import { IExtractionTableStudy } from './ExtractionTable'; -import { ArrowDownward } from '@mui/icons-material'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; - -export const ExtractionTableAuthorCell: React.FC> = ( - props -) => { - const value = props.getValue(); - return {value}; -}; - -export const ExtractionTableAuthorHeader: React.FC< - HeaderContext -> = ({ table, column }) => { - const isSorted = column.getIsSorted(); - - return ( - - - { - if (!!isSorted) { - table.resetSorting(); - } else { - table.setSorting([{ id: 'authors', desc: true }]); - } - }} - > - Authors - - - {!!isSorted && ( - <> - {isSorted === 'asc' ? ( - table.setSorting([{ id: 'authors', desc: true }])} - > - - - ) : ( - table.setSorting([{ id: 'authors', desc: false }])} - > - - - )} - - )} - - ); -}; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableDOI.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableDOI.tsx deleted file mode 100644 index f4a21dd4..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableDOI.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Box, IconButton, Link, Tooltip, Typography } from '@mui/material'; -import { CellContext, HeaderContext } from '@tanstack/react-table'; -import { IExtractionTableStudy } from './ExtractionTable'; -import { ArrowDownward } from '@mui/icons-material'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; - -export const ExtractionTableDOICell: React.FC> = ( - props -) => { - const value = props.getValue(); - return ( - - {value} - - ); -}; - -export const ExtractionTableDOIHeader: React.FC> = ({ - table, - column, -}) => { - const isSorted = column.getIsSorted(); - return ( - - - { - if (!!isSorted) { - table.resetSorting(); - } else { - table.setSorting([{ id: 'doi', desc: true }]); - } - }} - > - DOI - - - {!!isSorted && ( - <> - {isSorted === 'asc' ? ( - table.setSorting([{ id: 'doi', desc: true }])} - > - - - ) : ( - table.setSorting([{ id: 'doi', desc: false }])} - > - - - )} - - )} - - ); -}; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableFilterInput.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableFilterInput.tsx deleted file mode 100644 index c9ac8c29..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableFilterInput.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Box } from '@mui/material'; -import { Column } from '@tanstack/react-table'; -import DebouncedTextField from 'components/DebouncedTextField'; -import { useCallback } from 'react'; -import { EExtractionStatus } from '../ExtractionPage'; -import { IExtractionTableStudy } from './ExtractionTable'; -import ExtractionTableJournalAutocomplete from './ExtractionTableJournalAutocomplete'; -import ExtractionTableStatusFilter from './ExtractionTableStatusFilter'; - -const ExtractionTableFilterInput: React.FC<{ column: Column }> = ({ - column, -}) => { - const columnFilterValue = column.getFilterValue(); - const { filterVariant } = column.columnDef.meta ?? {}; - - const handleChangeAutocomplete = useCallback( - (event: string | null | undefined) => { - column.setFilterValue(event ?? null); - }, - [column] - ); - - if (filterVariant === 'status-select') { - return ( - - ); - } else if (filterVariant === 'journal-autocomplete') { - return ( - - ); - } else { - return ( - - - - ); - } -}; - -export default ExtractionTableFilterInput; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableJournal.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableJournal.tsx deleted file mode 100644 index d76bc065..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableJournal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Box, IconButton, Link, Tooltip, Typography } from '@mui/material'; -import { CellContext, HeaderContext } from '@tanstack/react-table'; -import { IExtractionTableStudy } from './ExtractionTable'; -import { ArrowDownward } from '@mui/icons-material'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; - -export const ExtractionTableJournalCell: React.FC> = ( - props -) => { - const value = props.getValue(); - return {value}; -}; - -export const ExtractionTableJournalHeader: React.FC< - HeaderContext -> = ({ table, column }) => { - const isSorted = column.getIsSorted(); - return ( - - - { - if (!!isSorted) { - table.resetSorting(); - } else { - table.setSorting([{ id: 'journal', desc: true }]); - } - }} - > - Journal - - - {!!isSorted && ( - <> - {isSorted === 'asc' ? ( - table.setSorting([{ id: 'journal', desc: true }])} - > - - - ) : ( - table.setSorting([{ id: 'journal', desc: false }])} - > - - - )} - - )} - - ); -}; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableJournalAutocomplete.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableJournalAutocomplete.tsx deleted file mode 100644 index 07225d68..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableJournalAutocomplete.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Autocomplete, Box, TextField } from '@mui/material'; -import { useGetStudysetById } from 'hooks'; -import { StudyReturn } from 'neurostore-typescript-sdk'; -import { useProjectExtractionStudysetId } from 'pages/Project/store/ProjectStore'; -import { useMemo } from 'react'; - -const ExtractionTableJournalAutocomplete: React.FC<{ - value: string; - onChange: (value: string | null) => void; -}> = ({ value, onChange }) => { - const studysetId = useProjectExtractionStudysetId(); - const { data: studyset } = useGetStudysetById(studysetId, true); - - const options = useMemo(() => { - if (!studyset) return []; - const journalsSet = new Set(); - (studyset.studies || []).forEach((study) => { - if ((study as StudyReturn)?.publication) { - journalsSet.add((study as StudyReturn)?.publication || ''); - } - }); - - return Array.from(journalsSet).sort(); - }, [studyset]); - - const handleChange = (event: any, value: string | null) => { - onChange(value); - }; - - return ( - - } - onChange={handleChange} - value={value || null} - options={options} - /> - - ); -}; - -export default ExtractionTableJournalAutocomplete; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableName.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableName.tsx deleted file mode 100644 index 66f9820e..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableName.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Box, IconButton, Link, Tooltip, Typography } from '@mui/material'; -import { CellContext, HeaderContext } from '@tanstack/react-table'; -import { IExtractionTableStudy } from './ExtractionTable'; -import { ArrowDownward } from '@mui/icons-material'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; - -export const ExtractionTableNameCell: React.FC> = ( - props -) => { - const value = props.getValue(); - return ( - - {value} - - ); -}; - -export const ExtractionTableNameHeader: React.FC> = ({ - table, - column, -}) => { - const isSorted = column.getIsSorted(); - return ( - - - { - if (!!isSorted) { - table.resetSorting(); - } else { - table.setSorting([{ id: 'name', desc: true }]); - } - }} - > - Name - - - {!!isSorted && ( - <> - {isSorted === 'asc' ? ( - table.setSorting([{ id: 'name', desc: true }])} - > - - - ) : ( - table.setSorting([{ id: 'name', desc: false }])} - > - - - )} - - )} - - ); -}; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTablePMID.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTablePMID.tsx deleted file mode 100644 index 8cdcb882..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTablePMID.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { Box, IconButton, Link, Tooltip, Typography } from '@mui/material'; -import { CellContext, HeaderContext } from '@tanstack/react-table'; -import { IExtractionTableStudy } from './ExtractionTable'; -import { ArrowDownward } from '@mui/icons-material'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; - -export const ExtractionTablePMIDCell: React.FC> = ( - props -) => { - const value = props.getValue(); - return {value}; -}; - -export const ExtractionTablePMIDHeader: React.FC> = ({ - column, - table, -}) => { - const isSorted = column.getIsSorted(); - return ( - - - { - if (!!isSorted) { - table.resetSorting(); - } else { - table.setSorting([{ id: 'pmid', desc: true }]); - } - }} - > - PMID - - - {!!isSorted && ( - <> - {isSorted === 'asc' ? ( - table.setSorting([{ id: 'pmid', desc: true }])} - > - - - ) : ( - table.setSorting([{ id: 'pmid', desc: false }])} - > - - - )} - - )} - - ); -}; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableStatus.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableStatus.tsx deleted file mode 100644 index c8696575..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableStatus.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { ArrowDownward, CheckCircle, QuestionMark } from '@mui/icons-material'; -import BookmarkIcon from '@mui/icons-material/Bookmark'; -import { Box, Button, ButtonGroup, IconButton, Link, Tooltip, Typography } from '@mui/material'; -import { CellContext, HeaderContext } from '@tanstack/react-table'; -import { useProjectExtractionAddOrUpdateStudyListStatus } from 'pages/Project/store/ProjectStore'; -import { EExtractionStatus } from '../ExtractionPage'; -import { IExtractionTableStudy } from './ExtractionTable'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; - -export const ExtractionTableStatusCell: React.FC< - CellContext -> = (props) => { - const status = props.getValue(); - - const updateStudyListStatus = useProjectExtractionAddOrUpdateStudyListStatus(); - - return ( - - - - - - - - ); -}; - -export const ExtractionTableStatusHeader: React.FC< - HeaderContext -> = ({ table, column }) => { - const isSorted = column.getIsSorted(); - return ( - - - { - if (!!isSorted) { - table.resetSorting(); - } else { - table.setSorting([{ id: 'status', desc: true }]); - } - }} - > - Status - - - {!!isSorted && ( - <> - {isSorted === 'asc' ? ( - table.setSorting([{ id: 'status', desc: true }])} - > - - - ) : ( - table.setSorting([{ id: 'status', desc: false }])} - > - - - )} - - )} - - ); -}; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableStatusFilter.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableStatusFilter.tsx deleted file mode 100644 index 7bf56c55..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableStatusFilter.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Bookmark, CheckCircle, QuestionMark } from '@mui/icons-material'; -import { - Box, - ListItemIcon, - ListItemText, - MenuItem, - Select, - SelectChangeEvent, - Typography, -} from '@mui/material'; -import { EExtractionStatus } from '../ExtractionPage'; - -const ExtractionStatusInput: React.FC = (props) => { - switch (props) { - case EExtractionStatus.COMPLETED: - return ( - - - Completed - - ); - case EExtractionStatus.SAVEDFORLATER: - return ( - - - Saved for Later - - ); - case EExtractionStatus.UNCATEGORIZED: - return ( - - - Unreviewed - - ); - case undefined: - default: - return ( - - All - - ); - } -}; - -const ExtractionTableStatusFilter: React.FC<{ - value: EExtractionStatus | null; - onChange: (val: string | null) => void; -}> = ({ value, onChange }) => { - const handleOnChange = (event: SelectChangeEvent) => { - onChange(event.target.value ? event.target.value : null); - }; - - return ( - - - - ); -}; - -export default ExtractionTableStatusFilter; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableYear.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableYear.tsx deleted file mode 100644 index 5d48a275..00000000 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableYear.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { ArrowDownward } from '@mui/icons-material'; -import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; -import { Box, IconButton, Link, Tooltip, Typography } from '@mui/material'; -import { CellContext, HeaderContext } from '@tanstack/react-table'; -import { IExtractionTableStudy } from './ExtractionTable'; - -export const ExtractionTableYearCell: React.FC> = ( - props -) => { - const value = props.getValue(); - return {value}; -}; - -export const ExtractionTableYearHeader: React.FC> = ({ - table, - column, -}) => { - const isSorted = column.getIsSorted(); - - return ( - - - { - if (!!isSorted) { - table.resetSorting(); - } else { - table.setSorting([{ id: 'year', desc: true }]); - } - }} - > - Year - - - {!!isSorted && ( - <> - {isSorted === 'asc' ? ( - table.setSorting([{ id: 'year', desc: true }])} - > - - - ) : ( - table.setSorting([{ id: 'year', desc: false }])} - > - - - )} - - )} - - ); -}; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ReadOnlyStudySummary.styles.ts b/compose/neurosynth-frontend/src/pages/Extraction/components/ReadOnlyStudySummary.styles.ts index dec88b2a..edf112d1 100644 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ReadOnlyStudySummary.styles.ts +++ b/compose/neurosynth-frontend/src/pages/Extraction/components/ReadOnlyStudySummary.styles.ts @@ -4,6 +4,14 @@ const StudyListItemStyles: Style = { listItem: { display: 'flex', padding: '10px', + width: 'calc(100% - 20px)', + height: 'calc(100% - 20px)', + transition: '0.1s ease-in-out', + ':hover': { + backgroundColor: '#efefef', + borderRadius: '8px', + cursor: 'pointer', + }, }, }; diff --git a/compose/neurosynth-frontend/src/pages/Extraction/components/ReadOnlyStudySummary.tsx b/compose/neurosynth-frontend/src/pages/Extraction/components/ReadOnlyStudySummary.tsx index 35d93070..bc02d3ea 100644 --- a/compose/neurosynth-frontend/src/pages/Extraction/components/ReadOnlyStudySummary.tsx +++ b/compose/neurosynth-frontend/src/pages/Extraction/components/ReadOnlyStudySummary.tsx @@ -13,7 +13,7 @@ import useUserCanEdit from 'hooks/useUserCanEdit'; const ReadOnlyStudySummaryVirtualizedItem: React.FC< StudyReturn & { - currentStatus: EExtractionStatus; + currentSelectedChip: EExtractionStatus; canEdit: boolean; style: React.CSSProperties; } @@ -39,19 +39,16 @@ const ReadOnlyStudySummaryVirtualizedItem: React.FC< }; const showMarkAsCompleteButton = - props.currentStatus === EExtractionStatus.UNCATEGORIZED || - props.currentStatus === EExtractionStatus.SAVEDFORLATER; + props.currentSelectedChip === EExtractionStatus.UNCATEGORIZED || + props.currentSelectedChip === EExtractionStatus.SAVEDFORLATER; const showMarkAsSaveForLaterButton = - props.currentStatus === EExtractionStatus.UNCATEGORIZED || - props.currentStatus === EExtractionStatus.COMPLETED; + props.currentSelectedChip === EExtractionStatus.UNCATEGORIZED || + props.currentSelectedChip === EExtractionStatus.COMPLETED; return ( - - + + {`${props.year ? `(${props.year}) ` : ''}${props.name}`} diff --git a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyAnnotationsHotTable.tsx b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyAnnotationsHotTable.tsx index f05a3bf3..77058652 100644 --- a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyAnnotationsHotTable.tsx +++ b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyAnnotationsHotTable.tsx @@ -1,7 +1,7 @@ import { HotTable } from '@handsontable/react'; import { Box } from '@mui/material'; import { CellChange, ChangeSource, RangeType } from 'handsontable/common'; -import { useMemo, useRef } from 'react'; +import { useRef } from 'react'; import { useAnnotationNoteKeys, useUpdateAnnotationNotes } from 'stores/AnnotationStore.actions'; import { sanitizePaste } from 'components/HotTables/HotTables.utils'; import useEditStudyAnnotationsHotTable from 'pages/Study/components/useEditStudyAnnotationsHotTable'; @@ -44,10 +44,6 @@ const EditStudyAnnotationsHotTable: React.FC<{ readonly?: boolean }> = ({ readon return true; }; - const memoizedData = useMemo(() => { - return JSON.parse(JSON.stringify(data || [])); - }, [data]); - return ( = ({ readon colWidths={colWidths} columns={columns} colHeaders={colHeaders} - data={memoizedData} + data={JSON.parse(JSON.stringify(data || []))} ref={hotTableRef} /> diff --git a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.spec.tsx b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.spec.tsx index 43370157..a63d069b 100644 --- a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.spec.tsx +++ b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.spec.tsx @@ -1,15 +1,16 @@ import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { useGetExtractionSummary, useGetStudysetById, useUserCanEdit } from 'hooks'; +import { useGetExtractionSummary, useGetStudysetById } from 'hooks'; +import { IStudyExtractionStatus } from 'hooks/projects/useGetProjects'; import { EExtractionStatus } from 'pages/Extraction/ExtractionPage'; import { useProjectExtractionAddOrUpdateStudyListStatus, - useProjectExtractionStudysetId, - useProjectId, + useProjectExtractionStudyStatusList, } from 'pages/Project/store/ProjectStore'; import { useStudyId } from 'pages/Study/store/StudyStore'; import { useNavigate } from 'react-router-dom'; import EditStudyToolbar from './EditStudyToolbar'; +import { useUserCanEdit } from 'hooks'; jest.mock('hooks'); jest.mock('react-router-dom'); @@ -17,11 +18,6 @@ jest.mock('pages/Project/store/ProjectStore.ts'); jest.mock('pages/Study/store/StudyStore.ts'); describe('EditStudyToolbar Component', () => { - beforeEach(() => { - jest.clearAllMocks(); - window.sessionStorage.clear(); - }); - it('should render', () => { render(); }); @@ -84,60 +80,67 @@ describe('EditStudyToolbar Component', () => { ); }); - it('should move to previous study when receiving state from the table', () => { + it('should move to previous study', () => { // ARRANGE - window.sessionStorage.setItem( - `projectid-extraction-table`, - JSON.stringify({ - columnFilters: [], - sorting: [], - studies: ['study-1', 'study-2', 'study-3', 'study-4'], - }) - ); + Storage.prototype.getItem = jest.fn().mockReturnValue(EExtractionStatus.SAVEDFORLATER); // mock localStorage (useStudyId as jest.Mock).mockReturnValue('study-2'); - (useProjectExtractionStudysetId as jest.Mock).mockReturnValue('studysetid'); - (useProjectId as jest.Mock).mockReturnValue('projectid'); (useUserCanEdit as jest.Mock).mockReturnValue(true); - - render(); - // ACT - userEvent.click(screen.getByTestId('ArrowBackIcon')); - // ASSERT - expect(useNavigate()).toHaveBeenCalledWith( - '/projects/projectid/extraction/studies/study-1/edit' - ); - }); - - it('should move to previous study if there is no state received from the table', () => { - // ARRANGE - (useStudyId as jest.Mock).mockReturnValue('study-3'); - (useProjectExtractionStudysetId as jest.Mock).mockReturnValue('studysetid'); - (useProjectId as jest.Mock).mockReturnValue('projectid'); - (useUserCanEdit as jest.Mock).mockReturnValue(true); - useGetStudysetById().data = { - studies: [{ id: 'study-2' }, { id: 'study-3' }, { id: 'study-4' }], + studies: [{ id: 'study-0' }, { id: 'study-0.5' }, { id: 'study-2' }, { id: 'study-3' }], }; + (useProjectExtractionStudyStatusList as jest.Mock).mockReturnValue([ + { + id: 'study-0', + status: EExtractionStatus.SAVEDFORLATER, + }, + { + id: 'study-0.5', + status: EExtractionStatus.UNCATEGORIZED, + }, + { + id: 'study-1', + status: EExtractionStatus.COMPLETED, + }, + { + id: 'study-2', + status: EExtractionStatus.SAVEDFORLATER, + }, + { + id: 'study-3', + status: EExtractionStatus.COMPLETED, + }, + ] as IStudyExtractionStatus[]); render(); // ACT userEvent.click(screen.getByTestId('ArrowBackIcon')); // ASSERT expect(useNavigate()).toHaveBeenCalledWith( - '/projects/projectid/extraction/studies/study-2/edit' + '/projects/project-id/extraction/studies/study-0/edit' ); }); - it('should disable the back button if on the first study', () => { + it('should disable if no previous study', () => { // ARRANGE + Storage.prototype.getItem = jest.fn().mockReturnValue(EExtractionStatus.SAVEDFORLATER); // mock localStorage (useStudyId as jest.Mock).mockReturnValue('study-2'); - (useProjectExtractionStudysetId as jest.Mock).mockReturnValue('studysetid'); - (useProjectId as jest.Mock).mockReturnValue('projectid'); - (useUserCanEdit as jest.Mock).mockReturnValue(true); - useGetStudysetById().data = { - studies: [{ id: 'study-2' }, { id: 'study-3' }, { id: 'study-4' }], + studies: [{ id: 'study-1' }, { id: 'study-2' }, { id: 'study-3' }], }; + (useProjectExtractionStudyStatusList as jest.Mock).mockReturnValue([ + { + id: 'study-1', + status: EExtractionStatus.UNCATEGORIZED, + }, + { + id: 'study-2', + status: EExtractionStatus.SAVEDFORLATER, + }, + { + id: 'study-3', + status: EExtractionStatus.COMPLETED, + }, + ] as IStudyExtractionStatus[]); render(); // ACT @@ -146,69 +149,77 @@ describe('EditStudyToolbar Component', () => { expect(arrowBackIcon).toBeDisabled(); }); - it('should move to the next study when receiving state from the table', () => { + it('should move to next study', () => { // ARRANGE - window.sessionStorage.setItem( - `projectid-extraction-table`, - JSON.stringify({ - columnFilters: [], - sorting: [], - studies: ['study-1', 'study-2', 'study-3', 'study-4'], - }) - ); + Storage.prototype.getItem = jest.fn().mockReturnValue(EExtractionStatus.COMPLETED); // mock localStorage (useStudyId as jest.Mock).mockReturnValue('study-2'); - (useProjectExtractionStudysetId as jest.Mock).mockReturnValue('studysetid'); - (useProjectId as jest.Mock).mockReturnValue('projectid'); - (useUserCanEdit as jest.Mock).mockReturnValue(true); - - render(); - // ACT - userEvent.click(screen.getByTestId('ArrowForwardIcon')); - // ASSERT - expect(useNavigate()).toHaveBeenCalledWith( - '/projects/projectid/extraction/studies/study-3/edit' - ); - }); - - it('should move to the next study if there is no state received from the table', () => { - // ARRANGE - (useStudyId as jest.Mock).mockReturnValue('study-3'); - (useProjectExtractionStudysetId as jest.Mock).mockReturnValue('studysetid'); - (useProjectId as jest.Mock).mockReturnValue('projectid'); - (useUserCanEdit as jest.Mock).mockReturnValue(true); - useGetStudysetById().data = { - studies: [{ id: 'study-2' }, { id: 'study-3' }, { id: 'study-4' }], + studies: [ + { id: 'study-1' }, + { id: 'study-2' }, + { id: 'study-3' }, + { id: 'study-4' }, + { id: 'study-5' }, + ], }; + (useProjectExtractionStudyStatusList as jest.Mock).mockReturnValue([ + { + id: 'study-1', + status: EExtractionStatus.UNCATEGORIZED, + }, + { + id: 'study-2', + status: EExtractionStatus.COMPLETED, + }, + { + id: 'study-3', + status: EExtractionStatus.SAVEDFORLATER, + }, + { + id: 'study-4', + status: EExtractionStatus.UNCATEGORIZED, + }, + { + id: 'study-5', + status: EExtractionStatus.COMPLETED, + }, + ] as IStudyExtractionStatus[]); render(); // ACT userEvent.click(screen.getByTestId('ArrowForwardIcon')); // ASSERT expect(useNavigate()).toHaveBeenCalledWith( - '/projects/projectid/extraction/studies/study-4/edit' + '/projects/project-id/extraction/studies/study-5/edit' ); }); - it('should disable the next button if on the last study', () => { + it('should disable if no next study', () => { // ARRANGE - window.sessionStorage.setItem( - `projectid-extraction-table`, - JSON.stringify({ - columnFilters: [], - sorting: [], - studies: ['study-1', 'study-2', 'study-3', 'study-4'], - }) - ); - (useStudyId as jest.Mock).mockReturnValue('study-4'); - (useProjectExtractionStudysetId as jest.Mock).mockReturnValue('studysetid'); - (useProjectId as jest.Mock).mockReturnValue('projectid'); - (useUserCanEdit as jest.Mock).mockReturnValue(true); + Storage.prototype.getItem = jest.fn().mockReturnValue(EExtractionStatus.SAVEDFORLATER); // mock localStorage + (useStudyId as jest.Mock).mockReturnValue('study-2'); + useGetStudysetById().data = { + studies: [{ id: 'study-1' }, { id: 'study-2' }, { id: 'study-3' }], + }; + (useProjectExtractionStudyStatusList as jest.Mock).mockReturnValue([ + { + id: 'study-1', + status: EExtractionStatus.UNCATEGORIZED, + }, + { + id: 'study-2', + status: EExtractionStatus.SAVEDFORLATER, + }, + { + id: 'study-3', + status: EExtractionStatus.UNCATEGORIZED, + }, + ] as IStudyExtractionStatus[]); render(); // ACT - const arrowForwardIcon = screen.getByTestId('ArrowForwardIcon').parentElement; + const arrowBackIcon = screen.getByTestId('ArrowForwardIcon').parentElement; // ASSERT - expect(arrowForwardIcon).toBeDisabled(); + expect(arrowBackIcon).toBeDisabled(); }); }); diff --git a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.tsx b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.tsx index 9ea015a5..d2f2fd7c 100644 --- a/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.tsx +++ b/compose/neurosynth-frontend/src/pages/Study/components/EditStudyToolbar.tsx @@ -3,75 +3,62 @@ import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; import BookmarkIcon from '@mui/icons-material/Bookmark'; import CheckIcon from '@mui/icons-material/Check'; import DoneAllIcon from '@mui/icons-material/DoneAll'; -import { Box, CircularProgress, IconButton, Tooltip, Typography } from '@mui/material'; -import { ColumnFiltersState, SortingState } from '@tanstack/react-table'; -import ProgressLoader from 'components/ProgressLoader'; -import GlobalStyles from 'global.styles'; -import { useGetExtractionSummary, useGetStudysetById, useUserCanEdit } from 'hooks'; +import { Box, CircularProgress, IconButton, Tooltip } from '@mui/material'; +import { useGetExtractionSummary, useGetStudysetById } from 'hooks'; import { StudyReturn } from 'neurostore-typescript-sdk'; import { EExtractionStatus } from 'pages/Extraction/ExtractionPage'; -import { IProjectPageLocationState } from 'pages/Project/ProjectPage'; import { useProjectExtractionAddOrUpdateStudyListStatus, - useProjectExtractionStudysetId, useProjectExtractionStudyStatus, + useProjectExtractionStudyStatusList, + useProjectExtractionStudysetId, useProjectId, useProjectMetaAnalysisCanEdit, useProjectUser, } from 'pages/Project/store/ProjectStore'; import { useStudyId } from 'pages/Study/store/StudyStore'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import EditStudyToolbarStyles from './EditStudyToolbar.styles'; +import { IProjectPageLocationState } from 'pages/Project/ProjectPage'; +import { useUserCanEdit } from 'hooks'; +import GlobalStyles from 'global.styles'; -const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = false }) => { - const navigate = useNavigate(); - const canEditMetaAnalyses = useProjectMetaAnalysisCanEdit(); +const getCurrSelectedChipText = (selectedChip: EExtractionStatus) => { + switch (selectedChip) { + case EExtractionStatus.UNCATEGORIZED: + return 'uncategorized'; + case EExtractionStatus.COMPLETED: + return 'completed'; + case EExtractionStatus.SAVEDFORLATER: + return 'saved for later'; + default: + return 'uncategorized'; + } +}; - const projectId = useProjectId(); - const extractionSummary = useGetExtractionSummary(projectId || ''); +const getCurrSelectedChip = (projectId: string | undefined) => { + return ( + (localStorage.getItem(`SELECTED_CHIP-${projectId}`) as EExtractionStatus) || + EExtractionStatus.UNCATEGORIZED + ); +}; +const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = false }) => { + const projectId = useProjectId(); const studyId = useStudyId(); const extractionStatus = useProjectExtractionStudyStatus(studyId || ''); - + const extractionSummary = useGetExtractionSummary(projectId || ''); + const studysetId = useProjectExtractionStudysetId(); + // nested msut be true so that we maintain to alphabetical study order + // if nested is false, we do not have access to study names and so will be given study Ids in random order + const { data: studyset } = useGetStudysetById(studysetId, true); + const studyStatusList = useProjectExtractionStudyStatusList(); + const navigate = useNavigate(); + const canEditMetaAnalyses = useProjectMetaAnalysisCanEdit(); const user = useProjectUser(); const canEdit = useUserCanEdit(user ?? undefined); - const studysetId = useProjectExtractionStudysetId(); - const { data, isLoading, isError } = useGetStudysetById(studysetId || '', true); - - // derived from the extraction table - const [studiesState, setStudiesState] = useState<{ - columnFilters: ColumnFiltersState; - sorting: SortingState; - studies: string[]; - }>({ - columnFilters: [], - sorting: [], - studies: [], - }); - - useEffect(() => { - const stateFromSessionStorage = sessionStorage.getItem(`${projectId}-extraction-table`); - if (!stateFromSessionStorage) { - setStudiesState((prev) => ({ - ...prev, - studies: (data?.studies || []).map((study) => (study as StudyReturn).id as string), - })); - } else { - try { - const parsedState = JSON.parse(stateFromSessionStorage) as { - columnFilters: ColumnFiltersState; - sorting: SortingState; - studies: string[]; - }; - setStudiesState(parsedState); - } catch (e) { - throw new Error('couldnt parse table state from session storage'); - } - } - }, [data?.studies, projectId]); - const updateStudyListStatus = useProjectExtractionAddOrUpdateStudyListStatus(); const handleClickStudyListStatus = (status: EExtractionStatus) => { @@ -80,22 +67,74 @@ const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = fal } }; + const getValidPrevStudyId = useCallback((): string | undefined => { + if (!studyset?.studies) return undefined; + + const CURR_SELECTED_CHIP_STATUS = getCurrSelectedChip(projectId); + const currStudyIndex = (studyset.studies || []).findIndex( + (study) => (study as StudyReturn)?.id === studyId + ); + if (currStudyIndex < 0) return undefined; + const map = new Map(); + studyStatusList.forEach((studyStatus) => { + map.set(studyStatus.id, studyStatus.status); + }); + + // go through all previous studies to find the next one before this current selected study that has the current selected chip status. + // This will also take care of the case where the current study selected is the first one + for (let i = currStudyIndex - 1; i >= 0; i--) { + const aStudy = studyset.studies[i] as StudyReturn; + if (!aStudy?.id) return undefined; + const aStudyStatus = map.get(aStudy.id) || EExtractionStatus.UNCATEGORIZED; + + if (aStudyStatus === CURR_SELECTED_CHIP_STATUS) return aStudy.id; + } + return undefined; + }, [projectId, studyId, studyStatusList, studyset]); + + const getValidNextStudyId = useCallback((): string | undefined => { + if (!studyset?.studies) return undefined; + + const CURR_SELECTED_CHIP_STATUS = getCurrSelectedChip(projectId); + const currStudyIndex = (studyset.studies || []).findIndex( + (study) => (study as StudyReturn)?.id === studyId + ); + if (currStudyIndex < 0) return undefined; + const map = new Map(); + studyStatusList.forEach((studyStatus) => { + map.set(studyStatus.id, studyStatus.status); + }); + + for (let i = currStudyIndex + 1; i <= studyset.studies.length; i++) { + const aStudy = studyset.studies[i] as StudyReturn; + if (!aStudy?.id) return undefined; + const aStudyStatus = map.get(aStudy.id) || EExtractionStatus.UNCATEGORIZED; + + if (aStudyStatus === CURR_SELECTED_CHIP_STATUS) return aStudy.id; + } + return undefined; + }, [projectId, studyId, studyStatusList, studyset]); + const handleMoveToPreviousStudy = () => { - const index = studiesState.studies.indexOf(studyId || ''); - const prevId = studiesState.studies[index - 1]; - if (!prevId) throw new Error('no previous study'); - canEdit - ? navigate(`/projects/${projectId}/extraction/studies/${prevId}/edit`) - : navigate(`/projects/${projectId}/extraction/studies/${prevId}`); + const prevId = getValidPrevStudyId(); + if (prevId) { + canEdit + ? navigate(`/projects/${projectId}/extraction/studies/${prevId}/edit`) + : navigate(`/projects/${projectId}/extraction/studies/${prevId}`); + } else { + throw new Error('no studies before this one'); + } }; const handleMoveToNextStudy = () => { - const index = studiesState.studies.indexOf(studyId || ''); - const nextId = studiesState.studies[index + 1]; - if (!nextId) throw new Error('no next study'); - canEdit - ? navigate(`/projects/${projectId}/extraction/studies/${nextId}/edit`) - : navigate(`/projects/${projectId}/extraction/studies/${nextId}`); + const nextId = getValidNextStudyId(); + if (nextId) { + canEdit + ? navigate(`/projects/${projectId}/extraction/studies/${nextId}/edit`) + : navigate(`/projects/${projectId}/extraction/studies/${nextId}`); + } else { + throw new Error('no studies after this one'); + } }; const handleContinueToMetaAnalysisCreation = () => { @@ -130,16 +169,33 @@ const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = fal }, [extractionSummary.completed, extractionSummary.total]); const hasPrevStudies = useMemo(() => { - const studies = studiesState.studies; - const index = studies.indexOf(studyId || ''); - return index - 1 >= 0; - }, [studiesState.studies, studyId]); + return getValidPrevStudyId() !== undefined; + }, [getValidPrevStudyId]); const hasNextStudies = useMemo(() => { - const studies = studiesState.studies; - const index = studies.indexOf(studyId || ''); - return index + 1 < studies.length; - }, [studiesState.studies, studyId]); + return getValidNextStudyId() !== undefined; + }, [getValidNextStudyId]); + + const currSelectedChipText = useMemo(() => { + const currSelectedChip = (localStorage.getItem(`SELECTED_CHIP-${projectId}`) || + EExtractionStatus.UNCATEGORIZED) as EExtractionStatus; + return getCurrSelectedChipText(currSelectedChip); + }, [projectId]); + + const prevNextArrowColor = useMemo(() => { + const currSelectedChip = (localStorage.getItem(`SELECTED_CHIP-${projectId}`) || + EExtractionStatus.UNCATEGORIZED) as EExtractionStatus; + switch (currSelectedChip) { + case EExtractionStatus.UNCATEGORIZED: + return 'warning.main'; + case EExtractionStatus.SAVEDFORLATER: + return 'info.main'; + case EExtractionStatus.COMPLETED: + return 'success.main'; + default: + return 'warning.main'; + } + }, [projectId]); return ( @@ -229,65 +285,58 @@ const EditStudyToolbar: React.FC<{ isViewOnly?: boolean }> = ({ isViewOnly = fal )} - - {isLoading ? ( - - - - ) : isError ? ( - - There was an error - - ) : ( - <> - - + + {/* tooltip cannot act on a disabled element so we need to add a span here */} + + - {/* tooltip cannot act on a disabled element so we need to add a span here */} - - - - - - - - - + + + + + + + {/* tooltip cannot act on a disabled element so we need to add a span here */} + + - {/* tooltip cannot act on a disabled element so we need to add a span here */} - - - - - - - - - )} + + + + + diff --git a/compose/neurosynth-frontend/src/pages/Study/store/__mocks__/StudyStore.ts b/compose/neurosynth-frontend/src/pages/Study/store/__mocks__/StudyStore.ts index ebec882a..a02df865 100644 --- a/compose/neurosynth-frontend/src/pages/Study/store/__mocks__/StudyStore.ts +++ b/compose/neurosynth-frontend/src/pages/Study/store/__mocks__/StudyStore.ts @@ -2,6 +2,4 @@ const useStudyId = jest.fn().mockReturnValue('study-id'); const useStudyName = jest.fn().mockResolvedValue('test-study-name'); -const useProjectId = jest.fn().mockReturnValue('project-id'); - -export { useStudyId, useStudyName, useProjectId }; +export { useStudyId, useStudyName }; diff --git a/compose/neurosynth-frontend/tsconfig.json b/compose/neurosynth-frontend/tsconfig.json index 46916174..b81f5299 100644 --- a/compose/neurosynth-frontend/tsconfig.json +++ b/compose/neurosynth-frontend/tsconfig.json @@ -18,5 +18,5 @@ "jsx": "react-jsx", "types": ["cypress", "node", "jest"] }, - "include": ["src", "cypress"] + "include": ["src"] }