From 0dd583a44d2dc0148c3a129ff98ae559342aaf50 Mon Sep 17 00:00:00 2001 From: James Kent Date: Thu, 19 Sep 2024 14:07:44 -0500 Subject: [PATCH] Revert "Revert "Revert "[FEAT] 518 extraction phase can we make the list of studies more spreadsheet like (#820)""" This reverts commit 499a3cb7cfc8e559a93119959c2c658730318d3a. --- .../cypress/e2e/pages/BaseStudyPage.cy.tsx | 2 +- .../cypress/e2e/pages/EditStudyPage.cy.tsx | 2 +- .../cypress/e2e/pages/LandingPage.cy.tsx | 2 +- .../cypress/e2e/pages/MetaAnalysisPage.cy.tsx | 2 +- .../e2e/pages/ProjectMetaAnalysesPage.cy.tsx | 2 +- .../cypress/e2e/pages/ProjectPage.cy.tsx | 2 +- .../e2e/pages/PublicStudiesPage.cy.tsx | 2 +- .../Curation/ImportStudiesDialog.cy.tsx | 12 +- .../Extraction/ExtractionTable.cy.tsx | 693 ----------- .../CreateSpecificationDialog.cy.tsx | 2 +- .../SleuthImport/DoSleuthImport.cy.tsx | 2 +- .../e2e/workflows/ingestion/Ingestion.cy.tsx | 2 +- .../cypress/fixtures/Extraction/project.json | 184 --- .../cypress/fixtures/studyset.json | 1084 +++++++++-------- compose/neurosynth-frontend/package-lock.json | 45 - compose/neurosynth-frontend/package.json | 1 - .../src/components/DebouncedTextField.tsx | 37 - .../components/Dialogs/ConfirmationDialog.tsx | 3 +- .../src/components/Search/SearchContainer.tsx | 2 +- .../src/pages/Extraction/ExtractionPage.tsx | 186 ++- .../components/ExtractionTable.module.css | 12 - .../Extraction/components/ExtractionTable.tsx | 429 ------- .../components/ExtractionTableAuthor.tsx | 59 - .../components/ExtractionTableDOI.tsx | 63 - .../components/ExtractionTableFilterInput.tsx | 52 - .../components/ExtractionTableJournal.tsx | 58 - .../ExtractionTableJournalAutocomplete.tsx | 43 - .../components/ExtractionTableName.tsx | 63 - .../components/ExtractionTablePMID.tsx | 59 - .../components/ExtractionTableStatus.tsx | 118 -- .../ExtractionTableStatusFilter.tsx | 91 -- .../components/ExtractionTableYear.tsx | 60 - .../components/ReadOnlyStudySummary.styles.ts | 8 + .../components/ReadOnlyStudySummary.tsx | 17 +- .../EditStudyAnnotationsHotTable.tsx | 8 +- .../components/EditStudyToolbar.spec.tsx | 191 +-- .../Study/components/EditStudyToolbar.tsx | 301 +++-- .../pages/Study/store/__mocks__/StudyStore.ts | 4 +- compose/neurosynth-frontend/tsconfig.json | 2 +- 39 files changed, 1063 insertions(+), 2842 deletions(-) delete mode 100644 compose/neurosynth-frontend/cypress/e2e/workflows/Extraction/ExtractionTable.cy.tsx delete mode 100644 compose/neurosynth-frontend/cypress/fixtures/Extraction/project.json delete mode 100644 compose/neurosynth-frontend/src/components/DebouncedTextField.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.module.css delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTable.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableAuthor.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableDOI.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableFilterInput.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableJournal.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableJournalAutocomplete.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableName.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTablePMID.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableStatus.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableStatusFilter.tsx delete mode 100644 compose/neurosynth-frontend/src/pages/Extraction/components/ExtractionTableYear.tsx diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/BaseStudyPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/BaseStudyPage.cy.tsx index bd1a7dfe7..7c2b320d0 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 586d7573d..cbe97e57f 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 cd6307abc..06a083d04 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 7c045940a..4b0f70bdc 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 a84f002d7..e9cdb41bb 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 3631c4342..ebd266a09 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 dc4a824d4..0cf70328f 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 607dfadf1..c7dfde86d 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 9a75dd51e..000000000 --- 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 7e42430c1..0be5448ee 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 c25014cf6..516a357cd 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 fb90cc61a..6a2d6ab77 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 c7d4209e3..000000000 --- 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 5f75b2f60..0335eaf3d 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 601442bdd..c5b527a48 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 6a105f7ee..178592a2e 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 45313e56d..000000000 --- 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 3fa9b23a7..aa1b98115 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 8f7f1dc6b..f9108fe85 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 395d35939..10901dfdb 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 7a6e3849d..000000000 --- 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 f61f96cd1..000000000 --- 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 117ab4ed8..000000000 --- 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 f4a21dd44..000000000 --- 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 c9ac8c292..000000000 --- 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 d76bc065e..000000000 --- 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 07225d682..000000000 --- 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 66f9820e4..000000000 --- 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 8cdcb8824..000000000 --- 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 c86965752..000000000 --- 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 7bf56c554..000000000 --- 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 5d48a2759..000000000 --- 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 dec88b2ab..edf112d12 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 35d930700..bc02d3eaa 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 f05a3bf36..770586526 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 43370157b..a63d069b0 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 9ea015a53..d2f2fd7c5 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 ebec882a1..a02df8653 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 46916174a..b81f5299e 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"] }