diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportFileFormat.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportFileFormat.cy.tsx new file mode 100644 index 000000000..4a229d081 --- /dev/null +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportFileFormat.cy.tsx @@ -0,0 +1,111 @@ +/// + +describe('ImportFileFormatDialog', () => { + beforeEach(() => { + cy.clearLocalStorage(); + cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); + cy.intercept('GET', `**/api/projects/*`, { + fixture: 'projects/projectExtractionStep', + }).as('projectFixture'); + cy.intercept('GET', `**/api/studysets/*`, { fixture: 'studyset' }).as('studysetFixture'); + }); + + describe('Import via File Format', () => { + beforeEach(() => { + cy.login('mocked').visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); + cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); + cy.contains('button', 'import studies').click(); + cy.contains('Import via File Format').click(); + cy.contains('button', 'next').click(); + }); + + it('should show the standard file import page', () => { + cy.contains(/enter data source/).should('be.visible'); + }); + + it('should disable the next button initially', () => { + cy.contains('button', 'next').should('be.disabled'); + }); + + it('should set the source and show the input', () => { + 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.contains('li', 'Scopus').click(); + cy.get('textarea[placeholder="paste in valid endnote, bibtex, or RIS syntax"]') + .click() + .type( + `%0 Journal Article\n%T Role of the anterior insula in task-level control and focal attention\n%A Nelson, Steven M\n%A Dosenbach, Nico UF\n%A Cohen, Alexander L\n%A Wheeler, Mark E\n%A Schlaggar, Bradley L\n%A Petersen, Steven E\n%J Brain structure and function\n%V 214\n%669-680\n%@ 1863-2653\n%D 2010\n%I Springer-Verlag\n`, + { + delay: 1, + } + ); + + cy.contains('button', 'next').should('not.be.disabled'); + }); + + it('should show an error message', () => { + 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'); + cy.contains(/Format is incorrect/).should('be.visible'); + }); + + it('should import studies', () => { + cy.get('input[role="combobox"]').click(); + cy.contains('li', 'Scopus').click(); + cy.get('textarea[placeholder="paste in valid endnote, bibtex, or RIS syntax"]') + .click() + .type( + `%0 Journal Article\n%T Role of the anterior insula in task-level control and focal attention\n%A Nelson, Steven M\n%A Dosenbach, Nico UF\n%A Cohen, Alexander L\n%A Wheeler, Mark E\n%A Schlaggar, Bradley L\n%A Petersen, Steven E\n%J Brain structure and function\n%V 214\n%669-680\n%@ 1863-2653\n%D 2010\n%I Springer-Verlag\n`, + { + delay: 1, + } + ); + + cy.contains('button', 'next').click(); + cy.get('input').type('my new import{enter}'); + cy.contains('button', 'next').click().url().should('include', '/projects/abc123/curation'); + }); + + it('should upload a onenote (ENW) file', () => { + cy.get('input[role="combobox"]').click(); + cy.contains('li', 'Scopus').click(); + cy.get('label[role="button"]').selectFile('cypress/fixtures/standardFiles/onenoteStudies.txt'); + cy.contains('button', 'next').should('be.visible'); + }); + + it('should upload a .RIS file and import successfully', () => { + cy.get('input[role="combobox"]').click(); + cy.contains('li', 'Scopus').click(); + cy.get('label[role="button"]').selectFile('cypress/fixtures/standardFiles/ris.ris'); + cy.contains('button', 'next').should('be.visible').and('not.be.disabled'); + cy.contains('button', 'next').click(); + }); + + it('should handle the duplicates in an import file', () => { + cy.get('input[role="combobox"]').click(); + cy.contains('li', 'Scopus').click(); + cy.get('label[role="button"]').selectFile('cypress/fixtures/standardFiles/duplicates.ris'); + cy.contains('button', 'next').should('be.visible').and('not.be.disabled'); + cy.contains('button', 'next').click(); + cy.contains('Click to view 1 imported studies').click(); + cy.contains('(2023). Manifold Learning for fMRI time-varying FC').should( + // doing this to enforce strict equals + ($el: JQuery | undefined) => { + if (!$el) throw new Error('Could not find element'); + expect($el.text()).to.equal('(2023). Manifold Learning for fMRI time-varying FC'); + } + ); + cy.contains(/^\bbioRxiv\b/).should('exist'); // \b is a word boundary which means there shouldnt be any other letters/words before and after + }); + + // TODO : create a test for importing bibtex file + // it('should import studies via a file', () => {}) + }); +}); diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportManualCreate.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportManualCreate.cy.tsx new file mode 100644 index 000000000..7aedfb44b --- /dev/null +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportManualCreate.cy.tsx @@ -0,0 +1,55 @@ +/// + +describe('ImportManualCreateDialog', () => { + beforeEach(() => { + cy.clearLocalStorage(); + cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); + cy.intercept('GET', `**/api/projects/*`, { + fixture: 'projects/projectExtractionStep', + }).as('projectFixture'); + cy.intercept('GET', `**/api/studysets/*`, { fixture: 'studyset' }).as('studysetFixture'); + }); + + describe('Manually create a new study', () => { + beforeEach(() => { + cy.login('mocked').visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); + cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); + cy.contains('button', 'import studies').click(); + cy.contains('Manually create a new study').click(); + cy.contains('button', 'next').click(); + }); + + it('should show the create new study page', () => { + cy.contains('label', 'Study Name *').should('be.visible'); + cy.contains('label', 'Authors').should('be.visible'); + cy.contains('label', 'DOI').should('be.visible'); + cy.contains('label', 'Journal').should('be.visible'); + cy.contains('label', 'PubMed ID').should('be.visible'); + cy.contains('label', 'PubMed Central ID').should('be.visible'); + cy.contains('label', 'Article Year').should('be.visible'); + cy.contains('label', 'article link').should('be.visible'); + cy.contains('label', 'Keywords').should('be.visible'); + cy.contains('label', 'select study data source *').should('be.visible'); + }); + + it('should be disabled initially', () => { + cy.contains('button', 'next').should('be.disabled'); + }); + + it('should be enabled when name and source are entered', () => { + cy.get('input[placeholder="My study name"]').click().type('new study'); + cy.get('input[role="combobox"]').click(); + cy.contains('li', 'Neurostore').click(); + cy.contains('button', 'next').should('not.be.disabled'); + }); + + it('should import studies', () => { + cy.get('input[placeholder="My study name"]').click().type('new study'); + cy.get('input[role="combobox"]').click(); + cy.contains('li', 'Neurostore').click(); + cy.contains('button', 'next').click(); + cy.get('input').type('my new import{enter}'); + cy.contains('button', 'next').click().url().should('include', '/projects/abc123/curation'); + }); + }); +}); diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportNeurostore.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportNeurostore.cy.tsx new file mode 100644 index 000000000..d6e2ead09 --- /dev/null +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportNeurostore.cy.tsx @@ -0,0 +1,52 @@ +/// + +describe('ImportStudiesDialog', () => { + beforeEach(() => { + cy.clearLocalStorage(); + cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); + cy.intercept('GET', `**/api/projects/*`, { + fixture: 'projects/projectExtractionStep', + }).as('projectFixture'); + cy.intercept('GET', `**/api/studysets/*`, { fixture: 'studyset' }).as('studysetFixture'); + }); + + describe('Search Neurostore', () => { + beforeEach(() => { + cy.login('mocked').visit('/projects/abc123/curation'); + cy.intercept('GET', '**/api/base-studies/**', { + fixture: 'baseStudies/baseStudiesWithResults', + }).as('baseStudiesFixture'); + cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); + cy.visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); + cy.contains('button', 'import studies').click(); + cy.contains('button', 'next').click(); + }); + + it('should show the neurostore search page', () => { + // we can target the table as the neurostore search is the only table HTML that appears in this workflow + cy.get('.MuiTableContainer-root').should('be.visible'); + }); + + it('should be disabled initially', () => { + cy.wait('@baseStudiesFixture').then((baseStudiesResponse) => { + cy.contains( + 'button', + `Import ${baseStudiesResponse.response?.body?.results?.length} studies from neurostore` + ).should('be.disabled'); + }); + }); + + it('should import studies', () => { + cy.get('input[type="text"]').type('neuron'); + cy.get('button').contains('Search').click(); + cy.wait('@baseStudiesFixture').then((baseStudiesResponse) => { + cy.contains( + 'button', + `Import ${baseStudiesResponse.response?.body?.results?.length} studies from neurostore` + ).click(); + }); + cy.get('input').type('my new import{enter}'); + cy.contains('button', 'next').click().url().should('include', '/projects/abc123/curation'); + }); + }); +}); diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportPubmed.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportPubmed.cy.tsx new file mode 100644 index 000000000..56646735b --- /dev/null +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportPubmed.cy.tsx @@ -0,0 +1,63 @@ +/// + +describe('ImportPubmedDialog', () => { + beforeEach(() => { + cy.clearLocalStorage(); + cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); + cy.intercept('GET', `**/api/projects/*`, { + fixture: 'projects/projectExtractionStep', + }).as('projectFixture'); + cy.intercept('GET', `**/api/studysets/*`, { fixture: 'studyset' }).as('studysetFixture'); + }); + + describe('Import via Pubmed IDs', () => { + beforeEach(() => { + cy.login('mocked').visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); + cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); + // not going to mock this for now as cypress does not seem to support XML fixtures + // cy.intercept('POST', 'https://eutils.ncbi.nlm.nih.gov/**', { + // fixture: 'NIHPMIDResponse', + // }).as('NIHPMIDFixture.xml'); + cy.contains('button', 'import studies').click(); + cy.contains(/PMID/).click(); + cy.contains('button', 'next').click(); + }); + + it('should show the pmid input page', () => { + cy.get('textarea[placeholder="Enter list of pubmed IDs separated by a newline"]').should('be.visible'); + }); + + it('should be disabled initially', () => { + cy.contains('button', 'next').should('be.disabled'); + }); + + it('should be enabled when an input is entered', () => { + cy.get('textarea[placeholder="Enter list of pubmed IDs separated by a newline"]') + .click() + .type('123{enter}456{enter}789'); + cy.contains('button', 'next').should('not.be.disabled'); + }); + + it('should show invalid for invalid input', () => { + cy.get('textarea[placeholder="Enter list of pubmed IDs separated by a newline"]') + .click() + .type('123{enter}456{enter}789A'); + cy.contains(/format is incorrect/); + cy.contains('button', 'next').should('be.disabled'); + }); + + it('should import studies', () => { + cy.get('textarea[placeholder="Enter list of pubmed IDs separated by a newline"]') + .click() + .type('123{enter}456{enter}789'); + cy.contains('button', 'next').click(); + cy.get('input').type('my new import{enter}'); + cy.contains('button', 'next').click().url().should('include', '/projects/abc123/curation'); + }); + + it('should import studies via a file', () => { + cy.get('label[role="button"]').selectFile('cypress/fixtures/pmids.txt'); + cy.contains('button', 'next').should('not.be.disabled'); + }); + }); +}); diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportSleuth.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportSleuth.cy.tsx new file mode 100644 index 000000000..cd810d82a --- /dev/null +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportSleuth.cy.tsx @@ -0,0 +1,512 @@ +/// +import { BaseStudy, BaseStudyReturn, StudyReturn } from 'neurostore-typescript-sdk'; +import baseStudiesSingleSleuthStudyResponse from '../../../fixtures/ImportSleuth/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json'; + +describe('ImportSleuthDialog', () => { + const neurostoreAPIBaseURL = Cypress.env('neurostoreAPIBaseURL'); + + beforeEach(() => { + cy.clearLocalStorage(); + cy.intercept('POST', `https://www.google-analytics.com/*/**`, {}).as('googleAnalyticsFixture'); + cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); + cy.intercept('GET', `**/api/projects/*`, { + fixture: 'projects/projectExtractionStep', + }).as('projectFixture'); + cy.intercept('GET', `**/api/studysets/*`, { fixture: 'studyset' }).as('studysetFixture'); + cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); + + cy.login('mocked').visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); + cy.contains('button', 'import studies').click(); + cy.contains('Sleuth').click(); + cy.contains('button', 'next').click(); + }); + + describe('upload page', () => { + it('should show the sleuth upload page', () => { + cy.contains(/Please ensure that your sleuth files are in the correct format before uploading/i).should( + 'be.visible' + ); + }); + + it('should disable upload by default', () => { + cy.contains('button', 'next').should('be.disabled'); + }); + }); + + describe('should handle invalid sleuth files', () => { + it('should upload a file and show invalid if not plain/text', () => { + cy.get('input[type="file"]').selectFile('cypress/fixtures/ImportSleuth/random.csv', { force: true }); + cy.contains('File should be .txt'); + }); + + it('should upload a file and show invalid with no reference', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileNoReference.txt', + { force: true } + ); + cy.contains('No coordinate reference'); + }); + + it('should upload a file and show invalid with no pubmed id or doi', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileNoPubmedIdOrDOI.txt', + { force: true } + ); + cy.contains('Either DOI or PMID is required'); + }); + + it('should upload a file and show invalid with invalid analysis', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileIncorrectAnalysis.txt', + { force: true } + ); + cy.contains('Unexpected format'); + }); + + it('should upload a file and show invalid with a semi colon', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileSemiColon.txt', + { force: true } + ); + cy.contains('Did you omit a colon or use a semi colon instead of a colon?'); + }); + + it('should upload a file and show invalid subjects', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileSubjects.txt', + { force: true } + ); + cy.contains('Unexpected format'); + }); + + it('should upload a file and show invalid format for wrong DOI format', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFilesBadDOIFormat.txt', + { force: true } + ); + cy.contains('Either DOI or PMID is required'); + }); + + it('should upload a file and show invalid format for wrong author experiment name string (no colon delimiter)', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileInvalidAuthor.txt', + { force: true } + ); + cy.contains('Unexpected format'); + }); + + it('should disable the button for an invalid file', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileInvalidAuthor.txt', + { force: true } + ); + cy.contains('button', 'next').should('be.disabled'); + }); + + it('should disable the button if there is an invalid file in a multi file upload', () => { + cy.get('input[type="file"]').selectFile( + [ + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + 'cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileInvalidAuthor.txt', + ], + { force: true } + ); + cy.contains('button', 'next').should('be.disabled'); + }); + }); + + describe('should upload valid sleuth files', () => { + it('should upload a valid file with a valid DOI', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + { force: true } + ); + cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); + }); + + it('should upload a valid file with a valid PMID', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithPMID.txt', + { force: true } + ); + cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); + }); + + it('should upload a valid file with a valid DOI and PMID', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOIAndPMID.txt', + { force: true } + ); + cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); + }); + + it('should upload multiple valid files', () => { + cy.get('input[type="file"]').selectFile( + [ + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithPMID.txt', + ], + { force: true } + ); + cy.get('[data-testid="InsertDriveFileIcon"]').should('have.length', 2); + }); + + it('should upload a valid sleuth file with windows line endings', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWindowsLineEndings.txt', + { force: true } + ); + cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); + cy.contains('button', 'next').should('be.enabled'); + }); + + it('should upload a valid sleuth file with wonky white space', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWonkyWhiteSpace.txt', + { force: true } + ); + cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); + cy.contains('button', 'next').should('be.enabled'); + }); + + it('should enable if a valid file is uploaded', () => { + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + { force: true } + ); + cy.contains('button', 'next').should('be.enabled'); + }); + + it('should enable if multiple valid files are uploaded', () => { + cy.get('input[type="file"]').selectFile( + [ + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithPMID.txt', + ], + { force: true } + ); + cy.contains('button', 'next').should('be.enabled'); + }); + }); + + describe('ingest sleuth files for a single file uploaded', () => { + beforeEach(() => { + cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/esearchSingleResponse.json', + delay: 500, + }).as('doiToPubmedQuery'); + cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/efetchSingleResponse.xml', + delay: 500, + }).as('pmidsFetch'); + cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json', + delay: 500, + }).as('baseStudiesIngestFixture'); + cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { + fixture: 'ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json', + }).as('analysesPostFixture'); + + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + { force: true } + ); + cy.contains('button', 'next').click(); + }); + + it('should show 0 progress initially', () => { + cy.get('[aria-valuenow="0"]').should('exist'); + }); + + it('should do the DOI => Pubmed query and show progress', () => { + cy.wait('@doiToPubmedQuery').then((res) => { + expect(res.request.query?.term).equals('some-doi.org'); + }); + cy.contains('Fetching study details').should('exist'); + }); + + it('should query pubmed for study details and show progress', () => { + cy.wait(['@doiToPubmedQuery', '@pmidsFetch']); + cy.contains(/Adding .+ studies into the database/i).should('exist'); + }); + + it('should fetch PMIDs', () => { + cy.wait(['@pmidsFetch']); + cy.contains(/Adding .+ studies into the database/i).should('be.visible'); + }); + + it('should have the expected arguments during ingestion', () => { + cy.wait('@baseStudiesIngestFixture').then((res) => { + expect(res.request.body[0]?.doi).equals('some-doi.org'); + }); + + cy.wait('@analysesPostFixture').then((res) => { + expect(res.request.body.study).equals( + baseStudiesSingleSleuthStudyResponse[0].versions.find( + (version: { username: string; id: string }) => version.username === 'test-user' + )?.id + ); + }); + }); + + it('should complete ingestion and show import complete', () => { + cy.wait('@analysesPostFixture'); + cy.contains(/import complete|ingestion complete/i).should('be.visible'); + }); + + it('should create stubs after ingestion', () => { + cy.wait('@analysesPostFixture'); + cy.contains('button', 'next').should('be.enabled'); + }); + }); + + describe('edge cases', () => { + it('should apply the pubmed details to the study if a matching pubmed study is found', () => { + cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/esearchSingleResponse.json', + }).as('doiToPubmedQuery'); + + cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/efetchSingleResponse.xml', + }).as('pmidsFetch'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', + }).as('baseStudiesIngestFixture'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { + fixture: 'ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json', + }).as('analysesPostFixture'); + + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + { force: true } + ); + cy.contains('button', 'next').click(); + + cy.wait('@baseStudiesIngestFixture').then((baseStudiesResponse) => { + const baseStudy = baseStudiesResponse.request.body[0]; + + // we should still have all the data from the original base study created from the sleuth import file + expect(baseStudy.authors).equals('Gerardin E,'); + expect(baseStudy.doi).equals('some-doi.org'); + expect(baseStudy.name).equals('The brains default mode network.'); + expect(baseStudy.year).equals(2000); + expect(baseStudy.publication).equals('Annual review of neuroscience'); + // additional data should be added from the pubmed query if it did not exist + expect(baseStudy.pmcid).equals('PMCTEST'); + expect(baseStudy.description).equals( + `\nThe brains default mode network consists of discrete, bilateral and symmetrical cortical areas, in the medial and lateral parietal, medial prefrontal, and medial and lateral temporal cortices of the human, nonhuman primate, cat, and rodent brains. Its discovery was an unexpected consequence of brain-imaging studies first performed with positron emission tomography in which various novel, attention-demanding, and non-self-referential tasks were compared with quiet repose either with eyes closed or with simple visual fixation. The default mode network consistently decreases its activity when compared with activity during these relaxed nontask states. The discovery of the default mode network reignited a longstanding interest in the significance of the brain's ongoing or intrinsic activity. Presently, studies of the brain's intrinsic activity, popularly referred to as resting-state studies, have come to play a major role in studies of the human brain in health and disease. The brain's default mode network plays a central role in this work.` + ); + }); + }); + + it('should do a POST request to /studies if the user does not own the study', () => { + cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/esearchSingleResponse.json', + }).as('doiToPubmedQuery'); + + cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/efetchSingleResponse.xml', + }).as('pmidsFetch'); + + cy.fixture('ImportSleuth/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json').then( + (baseStudiesResponse: BaseStudy[]) => { + cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, [ + { + ...baseStudiesResponse[0], + versions: [ + ...(baseStudiesResponse[0].versions as StudyReturn[]).map((v) => ({ + ...v, + user: 'other-user-sub', + })), + ], + user: 'other-user-sub', + }, + ]).as('baseStudiesIngestFixture'); + } + ); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { + fixture: 'ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json', + }).as('analysesPostFixture'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/studySingleSleuthStudyResponse.json', + }).as('studyPostFixture'); + + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + { force: true } + ); + cy.contains('button', 'next').click(); + + cy.get('@studyPostFixture').should('exist'); + }); + + it('should not do a POST request to /studies if the user does own the study', () => { + cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/esearchSingleResponse.json', + }).as('doiToPubmedQuery'); + + cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/efetchSingleResponse.xml', + }).as('pmidsFetch'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json', + }).as('baseStudiesIngestFixture'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { + fixture: 'ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json', + }).as('analysesPostFixture'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/studySingleSleuthStudyResponse.json', + }).as('studyPostFixture'); + + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + { force: true } + ); + cy.contains('button', 'next').click(); + + cy.get('@studyPostFixture.all').should('have.length', 0); + }); + + it('should do a POST request to /studies with multiple analyses if the user does not own the study', () => { + cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/esearchSingleResponse.json', + }).as('doiToPubmedQuery'); + + cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/efetchSingleResponse.xml', + }).as('pmidsFetch'); + + cy.fixture('ImportSleuth/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json').then( + (baseStudiesResponse: BaseStudyReturn[]) => { + cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, [ + ...baseStudiesResponse.map((baseStudy) => ({ + ...baseStudy, + user: 'other-user-sub', + versions: baseStudy.versions?.map((v: StudyReturn) => ({ + ...v, + user: 'other-user-sub', + })), + })), + ]).as('baseStudiesIngestFixture'); + } + ); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { + fixture: 'ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json', + }).as('analysesPostFixture'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/studySingleSleuthStudyResponse.json', + }).as('studyPostFixture'); + + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileMultipleExperimentsSameDOI.txt', + { force: true } + ); + cy.contains('button', 'next').click(); + + cy.wait('@studyPostFixture').then((interception) => { + expect(interception.request.body.analyses.length).to.equal(3); + }); + }); + + it('should do a POST request to /analyses if the user does own the study', () => { + cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/esearchSingleResponse.json', + }).as('doiToPubmedQuery'); + + cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/efetchSingleResponse.xml', + }).as('pmidsFetch'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', + }).as('baseStudiesIngestFixture'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { + fixture: 'ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json', + }).as('analysesPostFixture'); + + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + { force: true } + ); + cy.contains('button', 'next').click(); + + cy.get('@analysesPostFixture').should('exist'); + }); + + it('should consolidate into a single study if there are multiple sleuth experiments that have the same DOI or ID', () => { + cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?**/**`, { + fixture: 'ImportSleuth/pubmedResponses/esearchSingleResponse.json', + }).as('doiToPubmedQuery'); + + cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/efetchSingleResponse.xml', + }).as('pmidsFetch'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', + }).as('baseStudiesIngestFixture'); + + cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { + fixture: 'ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json', + }).as('analysesPostFixture'); + + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileMultipleExperimentsSameDOI.txt', + { force: true } + ); + cy.contains('button', 'next').click(); + + cy.wait('@baseStudiesIngestFixture').then((baseStudiesResponse) => { + expect(baseStudiesResponse.request?.body.length).equals(1); + }); + }); + }); + + describe('ingest multiple sleuth files', () => { + it('should successfully upload and ingest multiple files', () => { + cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/esearchSingleResponse.json', + }).as('doiToPubmedQuery'); + cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { + fixture: 'ImportSleuth/pubmedResponses/efetchSingleResponse.xml', + }).as('pmidsFetch'); + cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { + fixture: 'ImportSleuth/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', + }).as('baseStudiesIngestFixture'); + cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { + fixture: 'ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json', + }).as('analysesPostFixture'); + + cy.get('input[type="file"]').selectFile( + [ + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt', + 'cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithPMID.txt', + ], + { force: true } + ); + cy.contains('button', 'next').click(); + + // Should process both files + cy.wait('@doiToPubmedQuery') + .wait('@pmidsFetch') + .wait('@baseStudiesIngestFixture') + .wait('@analysesPostFixture') + .wait('@pmidsFetch') + .wait('@baseStudiesIngestFixture') + .wait('@analysesPostFixture'); + + cy.contains(/import complete|ingestion complete/i).should('be.visible'); + cy.contains('button', 'next').should('be.enabled'); + }); + }); +}); diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportStudies.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportStudies.cy.tsx index 78a5bf3cb..5523fe892 100644 --- a/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportStudies.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/workflows/Curation/ImportStudies.cy.tsx @@ -1,5 +1,4 @@ /// -import { expect } from 'chai'; describe('ImportStudiesDialog', () => { beforeEach(() => { @@ -22,240 +21,7 @@ describe('ImportStudiesDialog', () => { cy.url().should('include', '/projects/abc123/curation/import'); }); - describe('Search Neurostore', () => { - beforeEach(() => { - cy.login('mocked').visit('/projects/abc123/curation'); - cy.intercept('GET', '**/api/base-studies/**', { - fixture: 'baseStudies/baseStudiesWithResults', - }).as('baseStudiesFixture'); - cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); - cy.visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); - cy.contains('button', 'import studies').click(); - cy.contains('button', 'next').click(); - }); - - it('should show the neurostore search page', () => { - // we can target the table as the neurostore search is the only table HTML that appears in this workflow - cy.get('.MuiTableContainer-root').should('be.visible'); - }); - - it('should be disabled initially', () => { - cy.wait('@baseStudiesFixture').then((baseStudiesResponse) => { - cy.contains( - 'button', - `Import ${baseStudiesResponse.response?.body?.results?.length} studies from neurostore` - ).should('be.disabled'); - }); - }); - - it('should import studies', () => { - cy.get('input[type="text"]').type('neuron'); - cy.get('button').contains('Search').click(); - cy.wait('@baseStudiesFixture').then((baseStudiesResponse) => { - cy.contains( - 'button', - `Import ${baseStudiesResponse.response?.body?.results?.length} studies from neurostore` - ).click(); - }); - cy.get('input').type('my new import{enter}'); - cy.contains('button', 'next').click().url().should('include', '/projects/abc123/curation'); - }); - }); - - describe('Import via Pubmed IDs', () => { - beforeEach(() => { - cy.login('mocked').visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); - cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); - // not going to mock this for now as cypress does not seem to support XML fixtures - // cy.intercept('POST', 'https://eutils.ncbi.nlm.nih.gov/**', { - // fixture: 'NIHPMIDResponse', - // }).as('NIHPMIDFixture.xml'); - cy.contains('button', 'import studies').click(); - cy.contains(/PMID/).click(); - cy.contains('button', 'next').click(); - }); - - it('should show the pmid input page', () => { - cy.get('textarea[placeholder="Enter list of pubmed IDs separated by a newline"]').should('be.visible'); - }); - - it('should be disabled initially', () => { - cy.contains('button', 'next').should('be.disabled'); - }); - - it('should be enabled when an input is entered', () => { - cy.get('textarea[placeholder="Enter list of pubmed IDs separated by a newline"]') - .click() - .type('123{enter}456{enter}789'); - cy.contains('button', 'next').should('not.be.disabled'); - }); - - it('should show invalid for invalid input', () => { - cy.get('textarea[placeholder="Enter list of pubmed IDs separated by a newline"]') - .click() - .type('123{enter}456{enter}789A'); - cy.contains(/format is incorrect/); - cy.contains('button', 'next').should('be.disabled'); - }); - - it('should import studies', () => { - cy.get('textarea[placeholder="Enter list of pubmed IDs separated by a newline"]') - .click() - .type('123{enter}456{enter}789'); - cy.contains('button', 'next').click(); - cy.get('input').type('my new import{enter}'); - cy.contains('button', 'next').click().url().should('include', '/projects/abc123/curation'); - }); - - it('should import studies via a file', () => { - cy.get('label[role="button"]').selectFile('cypress/fixtures/pmids.txt'); - cy.contains('button', 'next').should('not.be.disabled'); - }); - }); - - describe('Manually create a new study', () => { - beforeEach(() => { - cy.login('mocked').visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); - cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); - cy.contains('button', 'import studies').click(); - cy.contains('Manually create a new study').click(); - cy.contains('button', 'next').click(); - }); - - it('should show the create new study page', () => { - cy.contains('label', 'Study Name *').should('be.visible'); - cy.contains('label', 'Authors').should('be.visible'); - cy.contains('label', 'DOI').should('be.visible'); - cy.contains('label', 'Journal').should('be.visible'); - cy.contains('label', 'PubMed ID').should('be.visible'); - cy.contains('label', 'PubMed Central ID').should('be.visible'); - cy.contains('label', 'Article Year').should('be.visible'); - cy.contains('label', 'article link').should('be.visible'); - cy.contains('label', 'Keywords').should('be.visible'); - cy.contains('label', 'select study data source *').should('be.visible'); - }); - - it('should be disabled initially', () => { - cy.contains('button', 'next').should('be.disabled'); - }); - - it('should be enabled when name and source are entered', () => { - cy.get('input[placeholder="My study name"]').click().type('new study'); - cy.get('input[role="combobox"]').click(); - cy.contains('li', 'Neurostore').click(); - cy.contains('button', 'next').should('not.be.disabled'); - }); - - it('should import studies', () => { - cy.get('input[placeholder="My study name"]').click().type('new study'); - cy.get('input[role="combobox"]').click(); - cy.contains('li', 'Neurostore').click(); - cy.contains('button', 'next').click(); - cy.get('input').type('my new import{enter}'); - cy.contains('button', 'next').click().url().should('include', '/projects/abc123/curation'); - }); - }); - - describe('Import via File Format', () => { - beforeEach(() => { - cy.login('mocked').visit('/projects/abc123/curation').wait('@projectFixture').wait('@studysetFixture'); - cy.intercept('PUT', '**/api/projects/abc123').as('updateProjectFixture'); - cy.contains('button', 'import studies').click(); - cy.contains('Import via File Format').click(); - cy.contains('button', 'next').click(); - }); - - it('should show the standard file import page', () => { - cy.contains(/enter data source/).should('be.visible'); - }); - - it('should disable the next button initially', () => { - cy.contains('button', 'next').should('be.disabled'); - }); - - it('should set the source and show the input', () => { - 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.contains('li', 'Scopus').click(); - cy.get('textarea[placeholder="paste in valid endnote, bibtex, or RIS syntax"]') - .click() - .type( - `%0 Journal Article\n%T Role of the anterior insula in task-level control and focal attention\n%A Nelson, Steven M\n%A Dosenbach, Nico UF\n%A Cohen, Alexander L\n%A Wheeler, Mark E\n%A Schlaggar, Bradley L\n%A Petersen, Steven E\n%J Brain structure and function\n%V 214\n%669-680\n%@ 1863-2653\n%D 2010\n%I Springer-Verlag\n`, - { - delay: 1, - } - ); - - cy.contains('button', 'next').should('not.be.disabled'); - }); - - it('should show an error message', () => { - 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'); - cy.contains(/Format is incorrect/).should('be.visible'); - }); - - it('should import studies', () => { - cy.get('input[role="combobox"]').click(); - cy.contains('li', 'Scopus').click(); - cy.get('textarea[placeholder="paste in valid endnote, bibtex, or RIS syntax"]') - .click() - .type( - `%0 Journal Article\n%T Role of the anterior insula in task-level control and focal attention\n%A Nelson, Steven M\n%A Dosenbach, Nico UF\n%A Cohen, Alexander L\n%A Wheeler, Mark E\n%A Schlaggar, Bradley L\n%A Petersen, Steven E\n%J Brain structure and function\n%V 214\n%669-680\n%@ 1863-2653\n%D 2010\n%I Springer-Verlag\n`, - { - delay: 1, - } - ); - - cy.contains('button', 'next').click(); - cy.get('input').type('my new import{enter}'); - cy.contains('button', 'next').click().url().should('include', '/projects/abc123/curation'); - }); - - it('should upload a onenote (ENW) file', () => { - cy.get('input[role="combobox"]').click(); - cy.contains('li', 'Scopus').click(); - cy.get('label[role="button"]').selectFile('cypress/fixtures/standardFiles/onenoteStudies.txt'); - cy.contains('button', 'next').should('be.visible'); - }); - - it('should upload a .RIS file and import successfully', () => { - cy.get('input[role="combobox"]').click(); - cy.contains('li', 'Scopus').click(); - cy.get('label[role="button"]').selectFile('cypress/fixtures/standardFiles/ris.ris'); - cy.contains('button', 'next').should('be.visible').and('not.be.disabled'); - cy.contains('button', 'next').click(); - }); - - it('should handle the duplicates in an import file', () => { - cy.get('input[role="combobox"]').click(); - cy.contains('li', 'Scopus').click(); - cy.get('label[role="button"]').selectFile('cypress/fixtures/standardFiles/duplicates.ris'); - cy.contains('button', 'next').should('be.visible').and('not.be.disabled'); - cy.contains('button', 'next').click(); - cy.contains('Click to view 1 imported studies').click(); - cy.contains('(2023). Manifold Learning for fMRI time-varying FC').should( - // doing this to enforce strict equals - ($el: JQuery | undefined) => { - if (!$el) throw new Error('Could not find element'); - expect($el.text()).to.equal('(2023). Manifold Learning for fMRI time-varying FC'); - } - ); - cy.contains(/^\bbioRxiv\b/).should('exist'); // \b is a word boundary which means there shouldnt be any other letters/words before and after - }); - - // TODO : create a test for importing bibtex file - // it('should import studies via a file', () => {}) - }); - - describe('Other features', () => { + describe('Finalize Import', () => { beforeEach(() => { cy.intercept('GET', '**/api/base-studies/**', { fixture: 'baseStudies/baseStudiesWithResults', diff --git a/compose/neurosynth-frontend/cypress/e2e/workflows/SleuthImport/DoSleuthImport.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/workflows/SleuthImport/DoSleuthImport.cy.tsx deleted file mode 100644 index 0f05d8d59..000000000 --- a/compose/neurosynth-frontend/cypress/e2e/workflows/SleuthImport/DoSleuthImport.cy.tsx +++ /dev/null @@ -1,852 +0,0 @@ -/// - -import analysesSingleSleuthStudyResponse from '../../../fixtures/DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json'; -import annotationsSingleSleuthStudyResponse from '../../../fixtures/DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json'; -import baseStudiesSingleSleuthStudyResponse from '../../../fixtures/DoSleuthImport/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json'; -import studysetsSingleSleuthStudyResponse from '../../../fixtures/DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json'; -import esearchSingleResponse from '../../../fixtures/DoSleuthImport/pubmedResponses/esearchSingleResponse.json'; - -const PATH = '/projects/new/sleuth'; - -describe('DoSleuthImport', () => { - const neurostoreAPIBaseURL = Cypress.env('neurostoreAPIBaseURL'); - const neurosynthAPIBaseURL = Cypress.env('neurosynthAPIBaseURL'); - - beforeEach(() => { - cy.clearLocalStorage(); - cy.intercept('GET', 'https://api.appzi.io/**', { fixture: 'appzi' }).as('appziFixture'); - - cy.intercept('POST', `https://www.google-analytics.com/*/**`, {}).as('googleAnalyticsFixture'); - }); - - it('should load successfully', () => { - cy.login('mocked').visit(PATH); - }); - - describe('upload sleuth file', () => { - beforeEach(() => { - cy.login('mocked').visit(PATH); - cy.contains('button', 'next').click(); - }); - - describe('should handle invalid sleuth files', () => { - it('should upload a file and show invalid with no reference', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileNoReference.txt', - { force: true } - ); - cy.contains('No coordinate reference'); - }); - - it('should upload a file and show invalid with no pubmed id or doi', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileNoPubmedIdOrDOI.txt', - { force: true } - ); - cy.contains('Either DOI or PMID is required'); - }); - - it('should upload a file and show invalid with invalid analysis', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileIncorrectAnalysis.txt', - { force: true } - ); - cy.contains('Unexpected format'); - }); - - it('should upload a file and show invalid with a semi colon', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileSemiColon.txt', - { force: true } - ); - cy.contains('Did you omit a colon or use a semi colon instead of a colon?'); - }); - - it('should upload a file and show invalid subjects', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileSubjects.txt', - { force: true } - ); - cy.contains('Unexpected format'); - }); - - it('should upload a file and show invalid format for wrong DOI format', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFilesBadDOIFormat.txt', - { force: true } - ); - cy.contains('Either DOI or PMID is required'); - }); - - it('should upload a file and show invalid format for wrong author experiment name string (no colon delimiter)', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileInvalidAuthor.txt', - { force: true } - ); - cy.contains('Unexpected format'); - }); - - it('should disable the button for an invalid file', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileInvalidAuthor.txt', - { force: true } - ); - cy.contains('button', 'create project').should('be.disabled'); - }); - - it('should disable the button if there is an invalid file in a multi file upload', () => { - cy.get('input[type="file"]').selectFile( - [ - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - 'cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileInvalidAuthor.txt', - ], - { force: true } - ); - cy.contains('button', 'create project').should('be.disabled'); - }); - }); - - describe('should upload valid sleuth files', () => { - it('should upload a valid file with a valid DOI', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - { force: true } - ); - cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); - }); - - it('should upload a valid file with a valid PMID', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithPMID.txt', - { force: true } - ); - cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); - }); - - it('should upload a valid file with a valid DOI and PMID', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOIAndPMID.txt', - { force: true } - ); - cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); - }); - - it('should upload multiple valid files', () => { - cy.get('input[type="file"]').selectFile( - [ - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithPMID.txt', - ], - { force: true } - ); - cy.get('[data-testid="InsertDriveFileIcon"]').should('have.length', 2); - }); - - it('should upload a valid sleuth file with windows line endings', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWindowsLineEndings.txt', - { force: true } - ); - cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); - cy.contains('button', 'create project').should('be.enabled'); - }); - - it('should upload a valid sleuth file with wonky white space', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWonkyWhiteSpace.txt', - { force: true } - ); - cy.get('[data-testid="InsertDriveFileIcon"]').should('exist').and('be.visible'); - cy.contains('button', 'create project').should('be.enabled'); - }); - - it('should enable if a valid file is uploaded', () => { - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - { force: true } - ); - cy.contains('button', 'create project').should('be.enabled'); - }); - - it('should enable if multiple valid files are uploaded', () => { - cy.get('input[type="file"]').selectFile( - [ - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithPMID.txt', - ], - { force: true } - ); - cy.contains('button', 'create project').should('be.enabled'); - }); - }); - - it('should be disabled by default', () => { - cy.contains('button', 'create project').should('be.disabled'); - }); - }); - - describe('build project for a single file uploaded', () => { - beforeEach(() => { - // synth API responses - cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/esearchSingleResponse.json', - delay: 500, - }).as('doiToPubmedQuery'); - cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/efetchSingleResponse.xml', - delay: 500, - }).as('pmidsFetch'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json', - delay: 500, - }).as('baseStudiesIngestFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json', - }).as('analysesPostFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/studysets/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json', - delay: 500, - }).as('studysetsPostFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/annotations/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json', - delay: 500, - }).as('annotationsPostFixture'); - - // compose API responses - cy.intercept('POST', `${neurosynthAPIBaseURL}/projects**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - delay: 500, - }).as('projectsPostFixture'); - - cy.intercept('GET', `${neurosynthAPIBaseURL}/projects/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsGetFixture'); - cy.login('mocked').visit(PATH); - cy.contains('button', 'next').click(); - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - { force: true } - ); - cy.contains('button', 'create project').click(); - }); - - it('should show 0 progress initially', () => { - cy.get('[aria-valuenow="0"]').should('exist'); - }); - - it('should move to the build project view, do the DOI => Pubmed query, and show progress', () => { - cy.wait('@doiToPubmedQuery').then((res) => { - expect(res.request.query?.term).equals('some-doi.org'); - }); - cy.contains('Fetching study details').should('exist'); - cy.get('[aria-valuenow="40"]').should('exist'); - }); - - it('should query pubmed for study details and show progress', () => { - cy.wait(['@doiToPubmedQuery', '@pmidsFetch']).then((res) => { - expect(res[1].request.body as string).contains(`id=${esearchSingleResponse.esearchresult.idlist[0]}`); - }); - cy.contains('Adding studies from').should('exist'); - cy.get('[aria-valuenow="40"]').should('exist'); - }); - - it('should fetch PMIDs and show progress', () => { - cy.wait(['@pmidsFetch']); - cy.contains('Adding studies from').should('be.visible'); - }); - - it('should have the expected arguments during ingestion', () => { - cy.wait('@baseStudiesIngestFixture').then((res) => { - expect(res.request.body[0]?.doi).equals('some-doi.org'); - }); - - cy.wait('@analysesPostFixture').then((res) => { - expect(res.request.body.study).equals( - baseStudiesSingleSleuthStudyResponse[0].versions.find((version) => version.username === 'test-user') - ?.id - ); - }); - }); - - it('should begin creating the studyset and show progress', () => { - cy.wait('@analysesPostFixture'); - cy.contains('Creating studyset').should('be.visible'); - cy.get('[aria-valuenow="85"]').should('exist'); - }); - - it('should begin creating the annotation and show progress', () => { - cy.wait('@studysetsPostFixture', { timeout: 10000 }).then((res) => { - expect(res.request.body.name).equals('Studyset for Untitled sleuth project'); - expect(res.request.body.studies).deep.equals(studysetsSingleSleuthStudyResponse.studies); - }); - cy.contains('Creating annotation...').should('be.visible'); - cy.get('[aria-valuenow="90"]').should('exist'); - }); - - it('should create the correct annotation keys and notes', () => { - cy.wait('@annotationsPostFixture').then((res) => { - const notes = [ - { - analysis: analysesSingleSleuthStudyResponse.id, - study: analysesSingleSleuthStudyResponse.study, - note: { - included: true, - validSleuthFileWithDOI_txt: true, - }, - }, - ]; - expect(res.request.body.name).equals('Annotation for Untitled sleuth project'); - expect(res.request.body.note_keys).deep.equals({ - included: 'boolean', - validSleuthFileWithDOI_txt: 'boolean', - }); - - expect(res.request.body.notes).deep.equals(notes); - }); - }); - - it('should show the import summary', () => { - cy.wait('@projectsPostFixture', { timeout: 10000 }).then((res) => { - expect(res.request.body.name).equals('Untitled sleuth project'); - expect(res.request.body.description).equals( - 'New project generated from files: validSleuthFileWithDOI.txt' - ); - expect(res.request.body.provenance.extractionMetadata.annotationId).equals( - annotationsSingleSleuthStudyResponse.id - ); - expect(res.request.body.provenance.extractionMetadata.studysetId).equals( - studysetsSingleSleuthStudyResponse.id - ); - }); - cy.contains('Import Complete').should('be.visible'); - }); - }); - - describe('build meta analyses', () => { - beforeEach(() => { - // synth API responses - cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/esearchSingleResponse.json', - }).as('doiToPubmedQuery'); - cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/efetchSingleResponse.xml', - }).as('pmidsFetch'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json', - }).as('baseStudiesIngestFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json', - }).as('analysesPostFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/studysets/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json', - }).as('studysetsPostFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/annotations/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json', - }).as('annotationsPostFixture'); - - // compose API responses - cy.intercept('POST', `${neurosynthAPIBaseURL}/projects**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsPostFixture'); - - cy.intercept('GET', `${neurosynthAPIBaseURL}/projects/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsGetFixture'); - - cy.intercept('POST', `${neurosynthAPIBaseURL}/specifications**`, { - fixture: 'DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json', - }).as('specificationPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/studysets**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json', - }).as('composeStudysetsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/annotations**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json', - }).as('composeAnnotationsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/meta-analyses**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeMetaAnalysesPostFixture'); - cy.intercept('GET', `${neurosynthAPIBaseURL}/meta-analyses**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeGetMetaAnalysesPostFixture'); - - cy.login('mocked').visit(PATH); - cy.contains('button', 'next').click(); - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - { force: true } - ); - cy.contains('button', 'create project').click(); - cy.wait([ - '@doiToPubmedQuery', - '@pmidsFetch', - '@baseStudiesIngestFixture', - '@analysesPostFixture', - '@studysetsPostFixture', - '@annotationsPostFixture', - '@projectsPostFixture', - ]); - cy.contains('button', 'next').click(); - }); - - it('should go to the meta analysis page', () => { - cy.contains('Would you like to create a meta-analysis').should('be.visible'); - }); - - it('should show the meta analyses options', () => { - cy.contains('button', 'Yes').click(); - cy.contains('ALE'); - cy.contains('MKDADensity'); - }); - - it('should select ALE and create an ALE meta analysis', () => { - cy.contains('button', 'Yes').click(); - cy.get('[type="radio"]').first().click(); - cy.contains('button', 'create') - .click() - .wait('@specificationPostFixture') - .then((res) => { - expect(res.request.body?.estimator?.type).equals('ALE'); - }); - }); - - it('should select MKDA and create an MKDA meta analysis', () => { - cy.contains('button', 'Yes').click(); - cy.get('[type="radio"]').eq(1).click({ force: true }); - cy.contains('button', 'create') - .click() - .wait('@specificationPostFixture') - .then((res) => { - expect(res.request.body?.estimator?.type).equals('MKDADensity'); - }); - }); - }); - - describe('edge cases', () => { - it('should apply the pubmed details to the study if a matching pubmed study is found', () => { - // this stuff exists just to make sure cypress doesnt send any real requests. They are not under test - // synth API responses - cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json', - }).as('analysesPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/studysets/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json', - }).as('studysetsPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/annotations/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json', - }).as('annotationsPostFixture'); - - // compose API responses - cy.intercept('POST', `${neurosynthAPIBaseURL}/projects**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsPostFixture'); - - cy.intercept('GET', `${neurosynthAPIBaseURL}/projects/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsGetFixture'); - - cy.intercept('POST', `${neurosynthAPIBaseURL}/specifications**`, { - fixture: 'DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json', - }).as('specificationPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/studysets**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json', - }).as('composeStudysetsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/annotations**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json', - }).as('composeAnnotationsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/meta-analyses**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeMetaAnalysesPostFixture'); - cy.intercept('GET', `${neurosynthAPIBaseURL}/meta-analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeGetMetaAnalysesPostFixture'); - - // this stuff is the important stuff needed for the test - cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/esearchSingleResponse.json', - }).as('doiToPubmedQuery'); - - cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/efetchSingleResponse.xml', - }).as('pmidsFetch'); - - // this is not important but is needed to finish the rest of the import - cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', - }).as('baseStudiesIngestFixture'); - - cy.login('mocked').visit(PATH); - cy.contains('button', 'next').click(); - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - { force: true } - ); - cy.contains('button', 'create project').click(); - - cy.wait('@baseStudiesIngestFixture').then((baseStudiesResponse) => { - const baseStudy = baseStudiesResponse.request.body[0]; - - // we should still have all the data from the original base study created from the sleuth import file - expect(baseStudy.authors).equals('Gerardin E,'); - expect(baseStudy.doi).equals('some-doi.org'); - expect(baseStudy.name).equals('The brains default mode network.'); - expect(baseStudy.year).equals(2000); - expect(baseStudy.publication).equals('Annual review of neuroscience'); - // additional data should be added from the pubmed query if it did not exist - expect(baseStudy.pmcid).equals('PMCTEST'); - expect(baseStudy.description).equals( - `\nThe brains default mode network consists of discrete, bilateral and symmetrical cortical areas, in the medial and lateral parietal, medial prefrontal, and medial and lateral temporal cortices of the human, nonhuman primate, cat, and rodent brains. Its discovery was an unexpected consequence of brain-imaging studies first performed with positron emission tomography in which various novel, attention-demanding, and non-self-referential tasks were compared with quiet repose either with eyes closed or with simple visual fixation. The default mode network consistently decreases its activity when compared with activity during these relaxed nontask states. The discovery of the default mode network reignited a longstanding interest in the significance of the brain's ongoing or intrinsic activity. Presently, studies of the brain's intrinsic activity, popularly referred to as resting-state studies, have come to play a major role in studies of the human brain in health and disease. The brain's default mode network plays a central role in this work.` - ); - }); - }); - - it('should go to the build step and do a POST request to /studies if the user does not own the study', () => { - // this stuff exists just to make sure cypress doesnt send any real requests. They are not under test - // synth API responses - cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/esearchSingleResponse.json', - }).as('doiToPubmedQuery'); - - cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/efetchSingleResponse.xml', - }).as('pmidsFetch'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', - }).as('baseStudiesIngestFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json', - }).as('analysesPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/studysets/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json', - }).as('studysetsPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/annotations/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json', - }).as('annotationsPostFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studySingleSleuthStudyResponse.json', - }).as('studyPostFixture'); - - // compose API responses - cy.intercept('POST', `${neurosynthAPIBaseURL}/projects**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsPostFixture'); - - cy.intercept('GET', `${neurosynthAPIBaseURL}/projects/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsGetFixture'); - - cy.intercept('POST', `${neurosynthAPIBaseURL}/specifications**`, { - fixture: 'DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json', - }).as('specificationPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/studysets**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json', - }).as('composeStudysetsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/annotations**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json', - }).as('composeAnnotationsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/meta-analyses**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeMetaAnalysesPostFixture'); - cy.intercept('GET', `${neurosynthAPIBaseURL}/meta-analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeGetMetaAnalysesPostFixture'); - - // this stuff is the important stuff needed for the test - - cy.login('mocked', { sub: 'other-user-sub' }).visit(PATH); - cy.contains('button', 'next').click(); - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - { force: true } - ); - cy.contains('button', 'create project').click(); - - cy.get('@studyPostFixture').should('exist'); - }); - - it('should go to the build step and do a POST request to /studies with multiple analyses if the user does not own the study', () => { - // this stuff exists just to make sure cypress doesnt send any real requests. They are not under test - // synth API responses - cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/esearchSingleResponse.json', - }).as('doiToPubmedQuery'); - - cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/efetchSingleResponse.xml', - }).as('pmidsFetch'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', - }).as('baseStudiesIngestFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json', - }).as('analysesPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/studysets/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json', - }).as('studysetsPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/annotations/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json', - }).as('annotationsPostFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studySingleSleuthStudyResponse.json', - }).as('studyPostFixture'); - - // compose API responses - cy.intercept('POST', `${neurosynthAPIBaseURL}/projects**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsPostFixture'); - - cy.intercept('GET', `${neurosynthAPIBaseURL}/projects/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsGetFixture'); - - cy.intercept('POST', `${neurosynthAPIBaseURL}/specifications**`, { - fixture: 'DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json', - }).as('specificationPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/studysets**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json', - }).as('composeStudysetsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/annotations**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json', - }).as('composeAnnotationsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/meta-analyses**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeMetaAnalysesPostFixture'); - cy.intercept('GET', `${neurosynthAPIBaseURL}/meta-analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeGetMetaAnalysesPostFixture'); - - // this stuff is the important stuff needed for the test - - cy.login('mocked', { sub: 'other-user-sub' }).visit(PATH); - cy.contains('button', 'next').click(); - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileMultipleExperimentsSameDOI.txt', - { force: true } - ); - cy.contains('button', 'create project').click(); - - cy.wait('@studyPostFixture').then((res) => { - expect(res.request.body.analyses.length).equals(3); - }); - }); - - it('should go to the build step and do a POST request to /analyses if the user does own the study. The best version should be selected', () => { - // this stuff exists just to make sure cypress doesnt send any real requests. They are not under test - // synth API responses - cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/esearchSingleResponse.json', - }).as('doiToPubmedQuery'); - - cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/efetchSingleResponse.xml', - }).as('pmidsFetch'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', - }).as('baseStudiesIngestFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json', - }).as('analysesPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/studysets/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json', - }).as('studysetsPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/annotations/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json', - }).as('annotationsPostFixture'); - - // compose API responses - cy.intercept('POST', `${neurosynthAPIBaseURL}/projects**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsPostFixture'); - - cy.intercept('GET', `${neurosynthAPIBaseURL}/projects/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsGetFixture'); - - cy.intercept('POST', `${neurosynthAPIBaseURL}/specifications**`, { - fixture: 'DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json', - }).as('specificationPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/studysets**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json', - }).as('composeStudysetsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/annotations**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json', - }).as('composeAnnotationsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/meta-analyses**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeMetaAnalysesPostFixture'); - cy.intercept('GET', `${neurosynthAPIBaseURL}/meta-analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeGetMetaAnalysesPostFixture'); - - // this stuff is the important stuff needed for the test - - cy.login('mocked').visit(PATH); - cy.contains('button', 'next').click(); - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - { force: true } - ); - cy.contains('button', 'create project').click(); - - cy.get('@analysesPostFixture').should('exist'); - }); - - it('should consolidate into a single study if there are multiple sleuth experiments that have the same DOI or ID', () => { - // this stuff exists just to make sure cypress doesnt send any real requests. They are not under test - // synth API responses - cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?**/**`, { - fixture: 'DoSleuthImport/pubmedResponses/esearchSingleResponse.json', - }).as('doiToPubmedQuery'); - - cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/efetchSingleResponse.xml', - }).as('pmidsFetch'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', - }).as('baseStudiesIngestFixture'); - - cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json', - }).as('analysesPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/studysets/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json', - }).as('studysetsPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/annotations/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json', - }).as('annotationsPostFixture'); - - // compose API responses - cy.intercept('POST', `${neurosynthAPIBaseURL}/projects**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsPostFixture'); - - cy.intercept('GET', `${neurosynthAPIBaseURL}/projects/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsGetFixture'); - - cy.intercept('POST', `${neurosynthAPIBaseURL}/specifications**`, { - fixture: 'DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json', - }).as('specificationPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/studysets**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json', - }).as('composeStudysetsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/annotations**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json', - }).as('composeAnnotationsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/meta-analyses**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeMetaAnalysesPostFixture'); - cy.intercept('GET', `${neurosynthAPIBaseURL}/meta-analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeGetMetaAnalysesPostFixture'); - - // this stuff is the important stuff needed for the test - - cy.login('mocked').visit(PATH); - cy.contains('button', 'next').click(); - cy.get('input[type="file"]').selectFile( - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileMultipleExperimentsSameDOI.txt', - { force: true } - ); - cy.contains('button', 'create project').click(); - - cy.wait('@baseStudiesIngestFixture').then((baseStudiesResponse) => { - expect(baseStudiesResponse.request?.body.length).equals(1); - }); - }); - }); - - it('should go through the whole workflow and successfully upload multiple files', () => { - // synth API responses - cy.intercept('GET', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/esearchSingleResponse.json', - }).as('doiToPubmedQuery'); - cy.intercept('POST', `*//eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi**`, { - fixture: 'DoSleuthImport/pubmedResponses/efetchSingleResponse.xml', - }).as('pmidsFetch'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/base-studies/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json', - }).as('baseStudiesIngestFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json', - }).as('analysesPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/studysets/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json', - }).as('studysetsPostFixture'); - cy.intercept('POST', `${neurostoreAPIBaseURL}/annotations/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json', - }).as('annotationsPostFixture'); - - // compose API responses - cy.intercept('POST', `${neurosynthAPIBaseURL}/projects**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsPostFixture'); - - cy.intercept('GET', `${neurosynthAPIBaseURL}/projects/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json', - }).as('projectsGetFixture'); - - cy.intercept('POST', `${neurosynthAPIBaseURL}/specifications**`, { - fixture: 'DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json', - }).as('specificationPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/studysets**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json', - }).as('composeStudysetsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/annotations**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json', - }).as('composeAnnotationsPostFixture'); - cy.intercept('POST', `${neurosynthAPIBaseURL}/meta-analyses**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeMetaAnalysesPostFixture'); - cy.intercept('GET', `${neurosynthAPIBaseURL}/meta-analyses/**`, { - fixture: 'DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json', - }).as('composeGetMetaAnalysesPostFixture'); - - cy.login('mocked').visit(PATH); - cy.contains('button', 'next').click(); - // upload page - cy.get('input[type="file"]').selectFile( - [ - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt', - 'cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithPMID.txt', - ], - { force: true } - ); - cy.contains('button', 'create project').click(); - - // build page - cy.wait('@doiToPubmedQuery') - .wait('@pmidsFetch') - .wait('@baseStudiesIngestFixture') - .wait('@analysesPostFixture') - .wait('@pmidsFetch') - .wait('@baseStudiesIngestFixture') - .wait('@analysesPostFixture') - .wait('@studysetsPostFixture') - .wait('@annotationsPostFixture') - .wait('@projectsPostFixture'); - - cy.contains('button', 'next').click(); - - // meta analysis page - cy.wait('@projectsGetFixture'); - cy.contains('button', 'Yes').click(); - cy.get('[type="radio"]').first().click(); - cy.contains('button', 'create').click(); - cy.wait('@specificationPostFixture') - .wait('@composeAnnotationsPostFixture') - .wait('@composeMetaAnalysesPostFixture'); - }); -}); diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/analysesSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/analysesSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/annotationsSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/annotationsSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/annotationsSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/baseStudiesMultipleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/baseStudiesSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/composeAnnotationsSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/composeGetMetaAnalysesSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/composeMetaAnalysesSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/composeStudysetsSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/projectsSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/projectsSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/projectsSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/specificationsSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/specificationsSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/specificationsSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/studySingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/studySingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/studySingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/studySingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/studysetsSingleSleuthStudyResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/neurosynthResponses/studysetsSingleSleuthStudyResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/neurosynthResponses/studysetsSingleSleuthStudyResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/pubmedResponses/efetchSingleResponse.xml b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/pubmedResponses/efetchSingleResponse.xml similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/pubmedResponses/efetchSingleResponse.xml rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/pubmedResponses/efetchSingleResponse.xml diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/pubmedResponses/esearchSingleResponse.json b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/pubmedResponses/esearchSingleResponse.json similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/pubmedResponses/esearchSingleResponse.json rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/pubmedResponses/esearchSingleResponse.json diff --git a/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/random.csv b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/random.csv new file mode 100644 index 000000000..ced7e793b --- /dev/null +++ b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/random.csv @@ -0,0 +1,12 @@ +id,product_name,category,price,quantity,last_updated +1,Widget Alpha,Electronics,29.99,45,2023-01-15 +2,Gadget Beta,Home & Garden,15.50,120,2023-02-20 +3,Tool Gamma,Automotive,89.99,23,2023-03-10 +4,Device Delta,Sports,44.25,67,2023-04-05 +5,Item Epsilon,Books,12.99,200,2023-05-18 +6,Product Zeta,Toys,33.75,88,2023-06-22 +7,Object Theta,Clothing,19.99,145,2023-07-30 +8,Thing Iota,Food,8.50,300,2023-08-12 +9,Article Kappa,Health,56.00,42,2023-09-25 +10,Commodity Lambda,Beauty,27.99,91,2023-10-01 + diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileIncorrectAnalysis.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileIncorrectAnalysis.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileIncorrectAnalysis.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileIncorrectAnalysis.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileInvalidAuthor.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileInvalidAuthor.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileInvalidAuthor.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileInvalidAuthor.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileNoDOI.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileNoDOI.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileNoDOI.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileNoDOI.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileNoPubmedIdOrDOI.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileNoPubmedIdOrDOI.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileNoPubmedIdOrDOI.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileNoPubmedIdOrDOI.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileNoReference.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileNoReference.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileNoReference.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileNoReference.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileSemiColon.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileSemiColon.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileSemiColon.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileSemiColon.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileSubjects.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileSubjects.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFileSubjects.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFileSubjects.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFilesBadDOIFormat.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFilesBadDOIFormat.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/invalidSleuthFilesBadDOIFormat.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/invalidSleuthFilesBadDOIFormat.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileMultipleExperimentsSameDOI.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileMultipleExperimentsSameDOI.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileMultipleExperimentsSameDOI.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileMultipleExperimentsSameDOI.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWindowsLineEndings.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWindowsLineEndings.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWindowsLineEndings.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWindowsLineEndings.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOI.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOI.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOIAndPMID.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOIAndPMID.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithDOIAndPMID.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithDOIAndPMID.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithPMID.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithPMID.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWithPMID.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWithPMID.txt diff --git a/compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWonkyWhiteSpace.txt b/compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWonkyWhiteSpace.txt similarity index 100% rename from compose/neurosynth-frontend/cypress/fixtures/DoSleuthImport/sleuthFiles/validSleuthFileWonkyWhiteSpace.txt rename to compose/neurosynth-frontend/cypress/fixtures/ImportSleuth/sleuthFiles/validSleuthFileWonkyWhiteSpace.txt diff --git a/compose/neurosynth-frontend/src/components/Buttons/CreateProjectButton.tsx b/compose/neurosynth-frontend/src/components/Buttons/CreateProjectButton.tsx index 80a22c751..8555387f7 100644 --- a/compose/neurosynth-frontend/src/components/Buttons/CreateProjectButton.tsx +++ b/compose/neurosynth-frontend/src/components/Buttons/CreateProjectButton.tsx @@ -1,12 +1,10 @@ import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import { ButtonGroup } from '@mui/material'; -import NavToolbarStyles from 'components/Navbar/NavToolbar.styles'; -import NeurosynthPopupMenu from 'components/NeurosynthPopupMenu'; -import { useRef } from 'react'; import LoadingButton from 'components/Buttons/LoadingButton'; +import NavToolbarStyles from 'components/Navbar/NavToolbar.styles'; import { useCreateProject } from 'hooks'; import { generateNewProjectData } from 'pages/Project/store/ProjectStore.helpers'; +import { useRef } from 'react'; import { useNavigate } from 'react-router-dom'; const CreateProjectButton: React.FC = () => { @@ -23,39 +21,23 @@ const CreateProjectButton: React.FC = () => { }; return ( - <> - + - } - text="NEW PROJECT" - /> - } - options={[ - { - label: 'Create project from sleuth file', - onClick: () => { - navigate('/projects/new/sleuth'); - }, - value: 'sleuth', - }, - ]} - /> - - + loaderColor="primary" + onClick={handleCreateProject} + isLoading={createProjectIsLoading} + sx={[NavToolbarStyles.menuItem, NavToolbarStyles.createProjectButton]} + startIcon={} + text="NEW PROJECT" + /> + ); }; diff --git a/compose/neurosynth-frontend/src/components/CodeSnippet/CodeSnippet.tsx b/compose/neurosynth-frontend/src/components/CodeSnippet/CodeSnippet.tsx index 6245eb295..2852aa28f 100644 --- a/compose/neurosynth-frontend/src/components/CodeSnippet/CodeSnippet.tsx +++ b/compose/neurosynth-frontend/src/components/CodeSnippet/CodeSnippet.tsx @@ -1,11 +1,12 @@ import { Box, Button } from '@mui/material'; +import { SystemStyleObject } from '@mui/system'; import { useState } from 'react'; import CodeSnippetStyles from './CodeSnippet.styles'; -const CodeSnippet: React.FC<{ linesOfCode: string[] }> = (props) => { +const CodeSnippet: React.FC<{ linesOfCode: string[]; noCopyButton?: boolean; sx?: SystemStyleObject }> = (props) => { const [copied, setCopied] = useState(false); - const copyToClipboard = (_event: React.MouseEvent) => { + const copyToClipboard = () => { setCopied(true); const codeString = props.linesOfCode.reduce((prev, curr, index) => { return index === 0 ? curr : `${prev}\n${curr}`; @@ -17,7 +18,7 @@ const CodeSnippet: React.FC<{ linesOfCode: string[] }> = (props) => { }; return ( - + {props.linesOfCode.map((code, index) => ( @@ -25,11 +26,13 @@ const CodeSnippet: React.FC<{ linesOfCode: string[] }> = (props) => { ))} - - - + {!props.noCopyButton && ( + + + + )} ); }; diff --git a/compose/neurosynth-frontend/src/components/Navbar/NavToolbar.tsx b/compose/neurosynth-frontend/src/components/Navbar/NavToolbar.tsx index 9ac1592e3..c4502553e 100644 --- a/compose/neurosynth-frontend/src/components/Navbar/NavToolbar.tsx +++ b/compose/neurosynth-frontend/src/components/Navbar/NavToolbar.tsx @@ -18,12 +18,7 @@ const NavToolbar: React.FC = (props) => { - + neurosynth compose diff --git a/compose/neurosynth-frontend/src/pages/BaseNavigation/BaseNavigation.tsx b/compose/neurosynth-frontend/src/pages/BaseNavigation/BaseNavigation.tsx index 4503d60e1..e30b442c3 100644 --- a/compose/neurosynth-frontend/src/pages/BaseNavigation/BaseNavigation.tsx +++ b/compose/neurosynth-frontend/src/pages/BaseNavigation/BaseNavigation.tsx @@ -1,26 +1,25 @@ import { Box, Link, Typography } from '@mui/material'; +import { ErrorBoundary } from '@sentry/react'; import ProgressLoader from 'components/ProgressLoader'; import AnnotationsPage from 'pages/Annotations/AnnotationsPage'; +import ProtectedMetaAnalysesRoute from 'pages/BaseNavigation/components/ProtectedMetaAnalysesRoute'; +import ProtectedProjectRoute from 'pages/BaseNavigation/components/ProtectedProjectRoute'; import CurationImportPage from 'pages/CurationImport/CurationImportPage'; import ExtractionPage from 'pages/Extraction/ExtractionPage'; +import ForbiddenPage from 'pages/Forbidden/Forbidden'; import NotFoundPage from 'pages/NotFound/NotFoundPage'; +import ProjectEditMetaAnalyses from 'pages/Project/components/ProjectEditMetaAnalyses'; +import ProjectViewMetaAnalyses from 'pages/Project/components/ProjectViewMetaAnalyses'; import ProjectPage from 'pages/Project/ProjectPage'; import BaseStudyPage from 'pages/Study/BaseStudyPage'; +import TermsAndConditions from 'pages/TermsAndConditions/TermsAndConditions'; import UserProfilePage from 'pages/UserProfile/UserProfilePage'; import React, { Suspense } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import LandingPage from '../LandingPage/LandingPage'; import BaseNavigationStyles from './BaseNavigation.styles'; -import ForbiddenPage from 'pages/Forbidden/Forbidden'; -import TermsAndConditions from 'pages/TermsAndConditions/TermsAndConditions'; -import ProjectEditMetaAnalyses from 'pages/Project/components/ProjectEditMetaAnalyses'; -import ProjectViewMetaAnalyses from 'pages/Project/components/ProjectViewMetaAnalyses'; -import ProtectedProjectRoute from 'pages/BaseNavigation/components/ProtectedProjectRoute'; import ProtectedRoute from './components/ProtectedRoute'; -import ProtectedMetaAnalysesRoute from 'pages/BaseNavigation/components/ProtectedMetaAnalysesRoute'; -import { ErrorBoundary } from '@sentry/react'; -const ImportSleuthPage = React.lazy(() => import('pages/SleuthImport/SleuthImportPage')); const EditStudyPage = React.lazy(() => import('pages/Study/EditStudyPage')); const ProjectStudyPage = React.lazy(() => import('pages/Study/ProjectStudyPage')); const StudiesPage = React.lazy(() => import('pages/Studies/StudiesPage')); @@ -74,16 +73,6 @@ const BaseNavigation: React.FC = () => { } /> - - - - - - } - /> { )} {activeStep === 1 && ( void; onImportStubs: (stubs: ICurationStubStudy[], unimportedStubs?: string[]) => void; - stubs: ICurationStubStudy[]; } const CurationImportDoImport: React.FC< @@ -20,23 +20,29 @@ const CurationImportDoImport: React.FC< onSetSearchCriteria: (searchCriteria: SearchCriteria) => void; onFileUpload: (fileName: string) => void; } -> = ({ mode, stubs, onImportStubs, onNavigate, onSetSearchCriteria, onFileUpload }) => { +> = ({ mode, onImportStubs, onNavigate, onSetSearchCriteria, onFileUpload }) => { switch (mode) { case EImportMode.NEUROSTORE_IMPORT: return ( ); + case EImportMode.SLEUTH_IMPORT: + return ( + + ); case EImportMode.PUBMED_IMPORT: return ( { + switch (importMode) { + case EImportMode.NEUROSTORE_IMPORT: { + let finalImportName = ''; + + if (searchCriteria?.genericSearchStr) { + finalImportName = `${searchCriteria.genericSearchStr}`; + } + + if (searchCriteria?.nameSearch) { + const nameStrSegment = searchCriteria.nameSearch; + finalImportName = + finalImportName.length > 0 + ? `${finalImportName} with name "${nameStrSegment}"` + : `name "${nameStrSegment}"`; + } + + if (searchCriteria?.journalSearch) { + const journalStrSegment = searchCriteria.journalSearch; + finalImportName = + finalImportName.length > 0 + ? `${finalImportName} in journal "${journalStrSegment}"` + : `journal "${journalStrSegment}"`; + } + + if (searchCriteria?.authorSearch) { + const authorStrSegment = searchCriteria.authorSearch; + finalImportName = + finalImportName.length > 0 + ? `${finalImportName} by author "${authorStrSegment}"` + : `author "${authorStrSegment}"`; + } + + if (searchCriteria?.descriptionSearch) { + const descriptionStrSegment = searchCriteria.descriptionSearch; + finalImportName = + finalImportName.length > 0 + ? `${finalImportName} with description "${descriptionStrSegment}"` + : `description "${descriptionStrSegment}"`; + } + + return finalImportName; + } + case EImportMode.FILE_IMPORT: { + const source = stubs[0].identificationSource; // this is safe because we know we must have at least one stub + const finalImportName = fileName ? `${fileName} from ${source.label}` : source.label; + return finalImportName; + } + case EImportMode.SLEUTH_IMPORT: { + return fileName ?? 'Sleuth Import'; + } + case EImportMode.MANUAL_CREATE: + return stubs[0].title || ''; + case EImportMode.PUBMED_IMPORT: + if (fileName) { + return `${fileName}`; + } else { + const pmids = stubs.reduce((acc, curr, index) => { + if (index === 0) return curr.pmid; + return `${acc}, ${curr.pmid}`; + }, ''); + + return pmids; + } + } +}; const CurationImportFinalize: React.FC<{ importMode: EImportMode; @@ -37,6 +112,10 @@ const CurationImportFinalize: React.FC<{ const navigate = useNavigate(); const projectId = useProjectId(); + const [importName, setImportName] = useState( + generateDefaultImportName(importMode, stubs, searchCriteria, fileName) + ); + const onFinalizeImport = (importName: string) => { // 1. create new import. Add import to all stubs. const newImport: IImport = { @@ -90,16 +169,48 @@ const CurationImportFinalize: React.FC<{ navigate(`/projects/${projectId}/curation`); }; + const handleClickNext = () => { + if (!importName) return; + onFinalizeImport(importName); + return; + }; + return ( - + + + + Give your import a name: + + setImportName(val.target.value)} + /> + + + + + + + + + + ); }; diff --git a/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportFinalizeNameAndReview.tsx b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportFinalizeNameAndReview.tsx deleted file mode 100644 index 76d725744..000000000 --- a/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportFinalizeNameAndReview.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { Box, Button, TextField, Typography } from '@mui/material'; -import { ENavigationButton } from 'components/Buttons/NavigationButtons'; -import { ICurationStubStudy } from 'pages/Curation/Curation.types'; -import { EImportMode } from 'pages/CurationImport/CurationImport.types'; -import { useState } from 'react'; -import CurationImportBaseStyles from './CurationImport.styles'; -import CurationImportFinalizeReview from './CurationImportFinalizeReview'; -import { SearchCriteria } from 'pages/Study/Study.types'; - -const generateDefaultImportName = ( - importMode: EImportMode, - stubs: ICurationStubStudy[], - searchCriteria: SearchCriteria | undefined, - fileName: string | undefined -) => { - switch (importMode) { - case EImportMode.NEUROSTORE_IMPORT: { - let finalImportName = ''; - - if (searchCriteria?.genericSearchStr) { - finalImportName = `${searchCriteria.genericSearchStr}`; - } - - if (searchCriteria?.nameSearch) { - const nameStrSegment = searchCriteria.nameSearch; - finalImportName = - finalImportName.length > 0 - ? `${finalImportName} with name "${nameStrSegment}"` - : `name "${nameStrSegment}"`; - } - - if (searchCriteria?.journalSearch) { - const journalStrSegment = searchCriteria.journalSearch; - finalImportName = - finalImportName.length > 0 - ? `${finalImportName} in journal "${journalStrSegment}"` - : `journal "${journalStrSegment}"`; - } - - if (searchCriteria?.authorSearch) { - const authorStrSegment = searchCriteria.authorSearch; - finalImportName = - finalImportName.length > 0 - ? `${finalImportName} by author "${authorStrSegment}"` - : `author "${authorStrSegment}"`; - } - - if (searchCriteria?.descriptionSearch) { - const descriptionStrSegment = searchCriteria.descriptionSearch; - finalImportName = - finalImportName.length > 0 - ? `${finalImportName} with description "${descriptionStrSegment}"` - : `description "${descriptionStrSegment}"`; - } - - return finalImportName; - } - case EImportMode.FILE_IMPORT: { - const source = stubs[0].identificationSource; // this is safe because we know we must have at least one stub - const finalImportName = fileName ? `${fileName} from ${source.label}` : source.label; - return finalImportName; - } - case EImportMode.MANUAL_CREATE: - return stubs[0].title || ''; - case EImportMode.PUBMED_IMPORT: - if (fileName) { - return `${fileName}`; - } else { - const pmids = stubs.reduce((acc, curr, index) => { - if (index === 0) return curr.pmid; - return `${acc}, ${curr.pmid}`; - }, ''); - - return pmids; - } - } -}; - -const CurationImportFinalizeNameAndReview: React.FC<{ - importMode: EImportMode; - onNavigate: (button: ENavigationButton) => void; - onNameImport: (name: string) => void; - stubs: ICurationStubStudy[]; - searchCriteria: SearchCriteria | undefined; - unimportedStubs: string[]; - fileName: string | undefined; -}> = ({ onNameImport, onNavigate, stubs, unimportedStubs, importMode, fileName, searchCriteria }) => { - const [importName, setImportName] = useState( - generateDefaultImportName(importMode, stubs, searchCriteria, fileName) - ); - - const handleClickNext = () => { - if (!importName) return; - onNameImport(importName); - return; - }; - - return ( - - - - Give your import a name: - - setImportName(val.target.value)} - /> - - - - - - - - - - - ); -}; - -export default CurationImportFinalizeNameAndReview; diff --git a/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSelectMethod.tsx b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSelectMethod.tsx index f1acd7a2a..15ec601a9 100644 --- a/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSelectMethod.tsx +++ b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSelectMethod.tsx @@ -59,17 +59,16 @@ const CurationImportSelectMethod: React.FC<{ - Manually create a new study + Import Sleuth File - Create a new study from scratch, manually filling in the title, authors, PMID, DOI, - etc + Import studies from a sleuth file into your project } @@ -93,6 +92,25 @@ const CurationImportSelectMethod: React.FC<{ } control={} /> + + Manually create a new study + + Create a new study from scratch, manually filling in the title, authors, PMID, DOI, + etc + + + } + control={} + /> diff --git a/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuth.tsx b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuth.tsx new file mode 100644 index 000000000..c3168222f --- /dev/null +++ b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuth.tsx @@ -0,0 +1,46 @@ +import { ENavigationButton } from 'components/Buttons/NavigationButtons'; +import { useState } from 'react'; +import { ISleuthFileUploadStubs } from '../helpers'; +import { IImportArgs } from './CurationImportDoImport'; +import CurationImportSleuthIngest from './CurationImportSleuthIngest'; +import CurationImportSleuthUpload from './CurationImportSleuthUpload'; +import { ICurationStubStudy } from 'pages/Curation/Curation.types'; + +const CurationImportSleuth: React.FC< + IImportArgs & { + onFileUpload: (fileName: string) => void; + } +> = ({ onFileUpload, onImportStubs, onNavigate }) => { + const [uploadFilePhase, setUploadFilePhase] = useState(true); + const [sleuthUploads, setSleuthUploads] = useState([]); + + const handleUploadFiles = (sleuthUploads: ISleuthFileUploadStubs[]) => { + setSleuthUploads(sleuthUploads); + onFileUpload( + sleuthUploads.reduce((acc, curr, index) => { + return index === 0 ? curr.fileName : `${acc}, ${curr.fileName}`; + }, '') + ); + setUploadFilePhase(false); + }; + + const handleOnStubSuploaded = (stubs: ICurationStubStudy[]) => { + onImportStubs(stubs); + onNavigate(ENavigationButton.NEXT); + }; + + if (uploadFilePhase) { + return ( + { + onNavigate(ENavigationButton.PREV); + }} + /> + ); + } + + return ; +}; + +export default CurationImportSleuth; diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportHelpDialog.tsx b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuthHint.tsx similarity index 50% rename from compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportHelpDialog.tsx rename to compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuthHint.tsx index bdad0a81c..a374086f5 100644 --- a/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportHelpDialog.tsx +++ b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuthHint.tsx @@ -1,8 +1,43 @@ import { Alert, Typography } from '@mui/material'; import CodeSnippet from 'components/CodeSnippet/CodeSnippet'; import HelpDialog from 'components/Dialogs/HelpDialog'; +import React from 'react'; -const SleuthImportHelpDialog = () => { +const hints = [ + { + instruction: + 'Begin the file with a Reference specifying coordinate space. This should only appear in the file once, at the top.', + example: '// Reference=MNI', + }, + { + instruction: + 'The next line should contain the DOI associated with the study. This field identifies the study that the data came from. At least one of either a DOI or a PubMedId is required.', + example: '// DOI=10.1016/1234567', + }, + { + instruction: + 'The next line should contain the PubMedId associated with the study. This field identifies the study that the data came from. At least one of either a DOI or a PubMedId is required.', + example: '// PubMedId=67123237', + }, + { + instruction: 'The next line should contain the author followed by the experiment, separated by a colon.', + example: '// Smith et al., 2019: Working Memory vs Baseline', + }, + { + instruction: 'The next line should contain the number of subjects.', + example: '// Subjects=23', + }, + { + instruction: 'The following lines should contain the tab separated coordinates', + example: '-7.5/t-8.5/t-9.5', + }, + { + instruction: 'Finally, a newline should be added as a delimiter, separating each of the studies in the file', + example: '', + }, +]; + +const CurationImportSleuthHint: React.FC = () => { return ( Neurosynth Compose expects files in a specific format. @@ -12,40 +47,19 @@ const SleuthImportHelpDialog = () => {
    -
  • - Begin the file with a Reference specifying coordinate space. This should only appear in the file - once, at the top. -
    - ex: // Reference=MNI" -
  • -
  • - The next line should contain the DOI associated with the study. This field identifies the study that - the data came from. At least one of either a DOI or a PubMedId is required. -
    - ex: // DOI=1234567 -
  • -
  • - The next line should contain the PubMedId associated with the study. This field identifies the study - that the data came from. At least one of either a DOI or a PubMedId is required. -
    - ex: // PubMedId=1234567 -
  • -
  • - The next line(s) should contain the author followed by the experiment, separated by a colon. -
    - ex: // Smith et al., 2019: Working Memory vs Baseline -
  • -
  • - The next line should contain the number of subjects. -
    - ex: // Subjects=23 -
  • -
  • - The following lines should contain the tab separated coordinates -
    - ex: -7.5/t-8.5/t-9.5 -
  • -
  • Finally, a newline should be added as a delimiter, separating each of the studies in the file
  • + {hints.map((hint, index) => ( +
  • + {hint.instruction} +
    + {hint.example && ( + + )} +
  • + ))}
Files should be plain text files with a .txt suffix. @@ -85,4 +99,4 @@ const SleuthImportHelpDialog = () => { ); }; -export default SleuthImportHelpDialog; +export default CurationImportSleuthHint; diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardBuild.tsx b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuthIngest.tsx similarity index 58% rename from compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardBuild.tsx rename to compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuthIngest.tsx index 1d30057cc..80ff4a946 100644 --- a/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardBuild.tsx +++ b/compose/neurosynth-frontend/src/pages/CurationImport/components/CurationImportSleuthIngest.tsx @@ -1,27 +1,22 @@ import { useAuth0 } from '@auth0/auth0-react'; import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; import { Box, Button, CircularProgress, LinearProgress, Typography } from '@mui/material'; -import { EPropertyType } from 'components/EditMetadata/EditMetadata.types'; import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; -import { useCreateAnnotation, useCreateProject, useCreateStudyset, useUpdateStudy } from 'hooks'; import useGetPubMedIdFromDOI from 'hooks/external/useGetPubMedIdFromDOI'; import useGetPubmedIDs from 'hooks/external/useGetPubMedIds'; import useIngest from 'hooks/studies/useIngest'; -import { BaseStudy, NoteCollectionReturn } from 'neurostore-typescript-sdk'; -import { EExtractionStatus } from 'pages/Extraction/ExtractionPage'; +import { BaseStudy } from 'neurostore-typescript-sdk'; +import CurationImportStyles from 'pages/CurationImport/components/CurationImport.styles'; import { useEffect, useRef, useState } from 'react'; import { applyPubmedStudyDetailsToBaseStudiesAndRemoveDuplicates, - createProjectHelper, - generateAnnotationForSleuthImport, -} from '../helpers'; -import CurationImportStyles from 'pages/CurationImport/components/CurationImport.styles'; -import { ingestBaseStudies, ISleuthFileUploadStubs, lookForPMIDsAndFetchStudyDetails, + sleuthIngestedStudiesToStubs, sleuthStubsToBaseStudies, -} from '../helpers'; +} from '../../CurationImport/helpers'; +import { ICurationStubStudy } from 'pages/Curation/Curation.types'; const updateUploadSummary = (sleuthUpload: ISleuthFileUploadStubs) => { const numCoordinatesImported = sleuthUpload.sleuthStubs.reduce((acc, curr) => { @@ -34,39 +29,27 @@ const updateUploadSummary = (sleuthUpload: ISleuthFileUploadStubs) => { }; }; -const SleuthImportWizardBuild: React.FC<{ +const CurationImportSleuthIngest: React.FC<{ sleuthUploads: ISleuthFileUploadStubs[]; - onNext: (projectId: string, studysetId: string, annotationId: string) => void; -}> = (props) => { + onStubsUploaded: (stubs: ICurationStubStudy[]) => void; +}> = ({ sleuthUploads, onStubsUploaded }) => { const { user } = useAuth0(); - const { queryImperatively } = useGetPubmedIDs([], false); + const { queryImperatively: getPubmedIds } = useGetPubmedIDs([], false); const { mutateAsync: getPubMedIdFromDOI } = useGetPubMedIdFromDOI(); - const [progressValue, setProgressValue] = useState(0); - const [progressText, setProgressText] = useState(''); const { mutateAsync: ingestAsync } = useIngest(); - const { mutateAsync: updateStudy } = useUpdateStudy(); - const { mutateAsync: createStudyset } = useCreateStudyset(); - const { mutateAsync: createAnnotation } = useCreateAnnotation(); - const { mutateAsync: createProject } = useCreateProject(); - const loadingState = useRef<{ - started: boolean; - }>({ - started: false, - }); - const { sleuthUploads, onNext } = props; - const [createdProjectComponents, setCreatedProjectComponents] = useState({ - projectId: '', - studysetId: '', - annotationId: '', - }); + + const hasStarted = useRef(false); + const [sleuthStudyStubs, setSleuthStudyStubs] = useState([]); const [isLoadingState, setIsLoadingState] = useState(true); const [isError, setIsError] = useState(false); + const [progressText, setProgressText] = useState(''); + const [progressValue, setProgressValue] = useState(0); useEffect(() => { if (sleuthUploads.length === 0) return; - if (loadingState.current.started) return; + if (hasStarted.current) return; - loadingState.current.started = true; + hasStarted.current = true; setIsLoadingState(true); const build = async (sleuthUploads: ISleuthFileUploadStubs[]) => { @@ -76,23 +59,16 @@ const SleuthImportWizardBuild: React.FC<{ setProgressValue(0); setProgressText('Starting upload...'); - const allAnnotationNotes: NoteCollectionReturn[] = []; - let allAnnotationNoteKeys: { [key: string]: EPropertyType } = { - included: EPropertyType.BOOLEAN, - }; - const uploads: { - fileName: string; - studyAnalysisList: { - studyId: string; - analysisId: string; - doi: string; - pmid: string; - }[]; - baseStudySleuthstubsWithDetails: BaseStudy[]; + const studyAnalysisList: { + studyId: string; + analysisId: string; + doi: string; + pmid: string; }[] = []; + const baseStudiesFromSleuthStubs: BaseStudy[] = []; let index = 0; - const percentageIncrement = 80 / sleuthUploads.length; + const percentageIncrement = 100 / sleuthUploads.length; for (const sleuthUpload of sleuthUploads) { setProgressText( `Fetching study details for studies within ${sleuthUpload.fileName} (if they exist)...` @@ -110,10 +86,10 @@ const SleuthImportWizardBuild: React.FC<{ Math.round((progress / 100) * (percentageIncrement / 2) + percentageAlreadyComplete) ); }, - queryImperatively + getPubmedIds ); - setProgressText(`Adding studies from ${sleuthUpload.fileName} into the database...`); + setProgressText(`Adding ${sleuthUpload.fileName} studies into the database...`); // 3. From the previous step, take our initial undetailed base studies and add details from pubmed // Remove duplicates. The ingestion endpoint will take these base studies and either @@ -125,6 +101,7 @@ const SleuthImportWizardBuild: React.FC<{ ); // 4. ingest the base studies and consolidate the sleuth stubs into Studies. + // We could potentially have multiple of the same study across the uploads // If N sleuth stubs have the same DOI/PMID, then a study object is created, with N analyses representing each sleuth stub const studyAnalysisObjects = await ingestBaseStudies( baseStudiesWithPubmedDetailsNoDuplicates, @@ -140,81 +117,13 @@ const SleuthImportWizardBuild: React.FC<{ ); } ); - - const { noteKeys, notes } = generateAnnotationForSleuthImport( - studyAnalysisObjects, - sleuthUpload.fileName - ); - allAnnotationNoteKeys = { ...allAnnotationNoteKeys, ...noteKeys }; - allAnnotationNotes.push(...notes); - - uploads.push({ - fileName: sleuthUpload.fileName, - studyAnalysisList: studyAnalysisObjects, - baseStudySleuthstubsWithDetails: baseStudiesWithPubmedDetailsNoDuplicates, - }); + studyAnalysisList.push(...studyAnalysisObjects); + baseStudiesFromSleuthStubs.push(...baseStudiesWithPubmedDetailsNoDuplicates); index++; } - // for multiple files, some annotation notes do not have all of the keys in the note object set, however - // the backend expects all note objects to have the properties that exist within noteKeys - Object.keys(allAnnotationNoteKeys).forEach((key) => { - allAnnotationNotes.forEach((noteObject) => { - const note = noteObject.note as { [key: string]: boolean }; - if (!note[key]) { - note[key] = false; - } - }); - }); - - setProgressValue(85); - setProgressText('Creating studyset...'); - - const createdStudyset = await createStudyset({ - name: `Studyset for Untitled sleuth project`, - description: '', - studies: uploads.reduce((acc, curr) => { - return [...acc, ...curr.studyAnalysisList.map((study) => study.studyId)]; - }, [] as string[]), - }); - if (!createdStudyset.data.id) throw new Error('Created studyset but found no ID'); - - setProgressValue(90); - setProgressText('Creating annotation...'); - - const createdAnnotation = await createAnnotation({ - source: 'neurosynth', - sourceId: undefined, - annotation: { - name: 'Annotation for Untitled sleuth project', - description: '', - note_keys: allAnnotationNoteKeys, - notes: allAnnotationNotes, - studyset: createdStudyset.data.id, - }, - }); - if (!createdAnnotation.data.id) throw new Error('Created annotation but found no ID'); - - setProgressValue(95); - setProgressText('Finalizing project...'); - - const createdProject = await createProjectHelper( - createdStudyset.data.id, - createdAnnotation.data.id, - uploads, - createProject - ); - if (!createdProject.data.id) throw new Error('Created project but found no ID'); - - setCreatedProjectComponents({ - projectId: createdProject.data.id, - studysetId: createdStudyset.data.id, - annotationId: createdAnnotation.data.id, - }); - - // set chip to completed in extraction page so that user is automatically brought to completed studies by default - const selectedChipLocalStorageKey = `SELECTED_CHIP-${createdProject.data.id}`; - localStorage.setItem(selectedChipLocalStorageKey, EExtractionStatus.COMPLETED); + const stubs = sleuthIngestedStudiesToStubs(studyAnalysisList, baseStudiesFromSleuthStubs); + setSleuthStudyStubs(stubs); setProgressValue(100); setProgressText('Complete...'); @@ -226,37 +135,16 @@ const SleuthImportWizardBuild: React.FC<{ }; build(sleuthUploads); - }, [ - createProject, - createStudyset, - ingestAsync, - onNext, - updateStudy, - createAnnotation, - sleuthUploads, - user, - queryImperatively, - getPubMedIdFromDOI, - ]); + }, [ingestAsync, onStubsUploaded, sleuthUploads, user, getPubmedIds, getPubMedIdFromDOI]); const handleNext = () => { - if ( - !createdProjectComponents.projectId || - !createdProjectComponents.studysetId || - !createdProjectComponents.annotationId - ) - return; - - onNext( - createdProjectComponents.projectId, - createdProjectComponents.studysetId, - createdProjectComponents.annotationId - ); + if (!sleuthStudyStubs) throw new Error('No sleuth study stubs found'); + onStubsUploaded(sleuthStudyStubs); }; return ( - + {isLoadingState ? ( void; onPrevious: () => void; }> = (props) => { @@ -111,14 +116,12 @@ const SleuthImportWizardUpload: React.FC<{ }, [sleuthFileUploads]); return ( - + - - Please ensure that your sleuth files are in the correct format before uploading + + Please ensure that your sleuth files are in the correct format before uploading - - - + - @@ -219,4 +217,4 @@ const SleuthImportWizardUpload: React.FC<{ ); }; -export default SleuthImportWizardUpload; +export default CurationImportSleuthUpload; diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.convert.ts b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.convert.ts similarity index 62% rename from compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.convert.ts rename to compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.convert.ts index fc74a6c5e..f6380f2af 100644 --- a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.convert.ts +++ b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.convert.ts @@ -1,4 +1,8 @@ import { stringToNumber } from 'helpers/utils'; +import { BaseStudy } from 'neurostore-typescript-sdk'; +import { ICurationStubStudy } from 'pages/Curation/Curation.types'; +import { defaultIdentificationSources } from 'pages/Project/store/ProjectStore.types'; +import { v4 as uuidv4 } from 'uuid'; import { cleanLine, extractAuthorsFromString, @@ -9,11 +13,6 @@ import { parseCoordinate, parseKeyVal, } from '.'; -import { v4 as uuidv4 } from 'uuid'; -import { ITag } from 'hooks/projects/useGetProjects'; -import { ICurationStubStudy } from 'pages/Curation/Curation.types'; -import { defaultIdentificationSources } from 'pages/Project/store/ProjectStore.types'; -import { BaseStudy } from 'neurostore-typescript-sdk'; const extractStubFromSleuthStudy = (sleuthStudy: string): ISleuthStub => { const studyStrings = sleuthStudy.split('\n'); @@ -78,60 +77,45 @@ export const sleuthUploadToStubs = (sleuthFile: string): Omit { - // although we know that each individual upload is deduplicates, - // its possible that there are multiple uploads with the same study. We want to deduplicate - // studies across all uploads so we const allIdentifiersSet = new Set(); - const studyResponsesToStubs: ICurationStubStudy[] = []; + const sleuthBaseStudiesToStubs: ICurationStubStudy[] = []; - for (const { fileName, studyAnalysisList, baseStudySleuthstubsWithDetails } of uploads) { - const tag: ITag = { - label: fileName, - id: uuidv4(), - isExclusionTag: false, - isAssignable: true, - }; - - baseStudySleuthstubsWithDetails.forEach( - ({ name, authors, pmid, pmcid, doi, year, publication, description }) => { - if ((pmid && allIdentifiersSet.has(pmid)) || (doi && allIdentifiersSet.has(doi))) { - return; - } - - if (pmid) allIdentifiersSet.add(pmid); - if (doi) allIdentifiersSet.add(doi); + for (const { name, authors, pmid, pmcid, doi, year, publication, description } of baseStudiesFromSleuthStubs) { + // although we know that each individual upload is deduplicated, + // its possible that there are multiple uploads with the same study. We want to deduplicate + // studies across all uploads so we only allow a unique pmid or doi + if ((pmid && allIdentifiersSet.has(pmid)) || (doi && allIdentifiersSet.has(doi))) return; - const correspondingStudyId = studyAnalysisList.find( - (studyAnalysisObject) => studyAnalysisObject.doi === doi || studyAnalysisObject.pmid === pmid - ); + if (pmid) allIdentifiersSet.add(pmid); + if (doi) allIdentifiersSet.add(doi); - studyResponsesToStubs.push({ - id: uuidv4(), - title: name || '', - authors: authors || '', - keywords: '', - pmid: pmid || '', - pmcid: pmcid || '', - doi: doi || '', - articleYear: year?.toString() || '', - journal: publication || '', - abstractText: description || '', - articleLink: '', - exclusionTag: null, - identificationSource: defaultIdentificationSources.sleuth, - tags: [tag], - neurostoreId: correspondingStudyId?.studyId || '', - }); - } + const correspondingStudyId = studyAnalysisList.find( + (studyAnalysisObject) => studyAnalysisObject.doi === doi || studyAnalysisObject.pmid === pmid ); + + sleuthBaseStudiesToStubs.push({ + id: uuidv4(), + title: name || '', + authors: authors || '', + keywords: '', + pmid: pmid || '', + pmcid: pmcid || '', + doi: doi || '', + articleYear: year?.toString() || '', + journal: publication || '', + abstractText: description || '', + articleLink: '', + exclusionTag: null, + identificationSource: defaultIdentificationSources.sleuth, + tags: [], + neurostoreId: correspondingStudyId?.studyId || '', + }); } - return studyResponsesToStubs; + + return sleuthBaseStudiesToStubs; }; export const sleuthStubsToBaseStudies = (sleuthStubs: ISleuthStub[]) => { diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.parse.ts b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.parse.ts similarity index 100% rename from compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.parse.ts rename to compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.parse.ts diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.project.ts b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.project.ts similarity index 100% rename from compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.project.ts rename to compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.project.ts diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.requests.ts b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.requests.ts similarity index 100% rename from compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.requests.ts rename to compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.requests.ts diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.types.ts b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.types.ts similarity index 100% rename from compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.types.ts rename to compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.types.ts diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.validate.ts b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.validate.ts similarity index 96% rename from compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.validate.ts rename to compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.validate.ts index ed67d4369..efce691f6 100644 --- a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/SleuthImport.validate.ts +++ b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/SleuthImport.validate.ts @@ -8,7 +8,7 @@ export const isCoordLine = (lineRaw: string) => { return /^\s*-?\d+(?:\.\d+)?\s+-?\d+(?:\.\d+)?\s+-?\d+(?:\.\d+)?\s*$/.test(line); }; -export const stringsAreValidFileFormat = (sleuthStudy: string): { isValid: boolean; errorMessage?: string } => { +export const studyChunkIsValid = (sleuthStudy: string): { isValid: boolean; errorMessage?: string } => { let containsDOI = false; let containsPMID = false; let containsAtLeastOneExperimentName = false; @@ -135,7 +135,7 @@ export const validateFileContents = (fileContents: string): { isValid: boolean; if (chunk.length) studyChunks.push(chunk.join('\n')); for (const sleuthStudy of studyChunks) { - const { isValid, errorMessage = '' } = stringsAreValidFileFormat(sleuthStudy); + const { isValid, errorMessage = '' } = studyChunkIsValid(sleuthStudy); if (!isValid) { return { isValid: false, diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/helpers/index.ts b/compose/neurosynth-frontend/src/pages/CurationImport/helpers/index.ts similarity index 100% rename from compose/neurosynth-frontend/src/pages/SleuthImport/helpers/index.ts rename to compose/neurosynth-frontend/src/pages/CurationImport/helpers/index.ts diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/SleuthImportPage.tsx b/compose/neurosynth-frontend/src/pages/SleuthImport/SleuthImportPage.tsx deleted file mode 100644 index b6e236ae4..000000000 --- a/compose/neurosynth-frontend/src/pages/SleuthImport/SleuthImportPage.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { useAuth0 } from '@auth0/auth0-react'; -import { Box, Step, StepLabel, Stepper } from '@mui/material'; -import NeurosynthBreadcrumbs from 'components/NeurosynthBreadcrumbs'; -import { useGuard } from 'hooks'; -import { useState } from 'react'; -import SleuthImportWizardBuild from 'pages/SleuthImport/components/SleuthImportWizardBuild'; -import SleuthImportWizardCreateMetaAnalyses from 'pages/SleuthImport/components/SleuthImportWizardCreateMetaAnalyses'; -import SleuthImportWizardIntroduction from 'pages/SleuthImport/components/SleuthImportWizardIntroduction'; -import SleuthImportWizardUpload from 'pages/SleuthImport/components/SleuthImportWizardUpload'; -import { ISleuthFileUploadStubs } from './helpers'; - -const SleuthImportPage: React.FC = () => { - const [activeStep, setActiveStep] = useState(0); - const [uploadedSleuthFiles, setUploadedSleuthFiles] = useState([]); - const [projectComponents, setProjectComponents] = useState({ - projectId: '', - studysetId: '', - annotationId: '', - }); - const { isLoading, isAuthenticated } = useAuth0(); - useGuard(`/`, 'You must be signed in to access this page.', !isAuthenticated, isLoading); - - const handleNextFromIntroduction = () => { - setActiveStep((prev) => prev + 1); - }; - - const handlePrevious = () => { - setActiveStep((prev) => prev - 1); - }; - - const handleNextFromUpload = (sleuthFiles: ISleuthFileUploadStubs[]) => { - setUploadedSleuthFiles(sleuthFiles); - setActiveStep((prev) => prev + 1); - }; - - const handleNextFromBuild = (createdProjectId: string, createdStudysetId: string, createdAnnotationId: string) => { - setProjectComponents({ - projectId: createdProjectId, - studysetId: createdStudysetId, - annotationId: createdAnnotationId, - }); - setActiveStep((prev) => prev + 1); - }; - - return ( - - - - - - - Introduction - - - Upload Sleuth File(s) - - - Build Project - - - Meta Analyses - - - - - {activeStep === 0 && } - {activeStep === 1 && ( - - )} - {activeStep === 2 && ( - - )} - {activeStep === 3 && ( - - )} - - - ); -}; - -export default SleuthImportPage; diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardCreateMetaAnalyses.tsx b/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardCreateMetaAnalyses.tsx deleted file mode 100644 index f0b726f86..000000000 --- a/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardCreateMetaAnalyses.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { IAlgorithmSelection } from 'pages/MetaAnalysis/components/CreateMetaAnalysisSpecificationDialogBase.types'; -import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; -import useGetProjectById from 'hooks/projects/useGetProjectById'; -import { useNavigate } from 'react-router-dom'; -import { useCreateAlgorithmSpecification } from 'hooks'; -import { EAnalysisType } from 'hooks/metaAnalyses/useCreateAlgorithmSpecification'; -import { - Typography, - ToggleButtonGroup, - ToggleButton, - FormControl, - RadioGroup, - FormControlLabel, - Radio, - Box, -} from '@mui/material'; -import CurationImportBaseStyles from 'pages/CurationImport/components/CurationImport.styles'; -import { - getDefaultValuesForTypeAndParameter, - metaAnalyticAlgorithms, -} from 'pages/MetaAnalysis/components/CreateMetaAnalysisSpecificationDialogConstants'; -import { IAutocompleteObject } from 'components/NeurosynthAutocomplete/NeurosynthAutocomplete'; -import { useMemo, useState } from 'react'; -import LoadingButton from 'components/Buttons/LoadingButton'; -import { getWeightAndConditionsForSpecification } from 'pages/MetaAnalysis/components/CreateMetaAnalysisSpecificationReview.helpers'; -import { EPropertyType } from 'components/EditMetadata/EditMetadata.types'; -import { useSnackbar } from 'notistack'; -import { useQueryClient } from 'react-query'; -import { ISleuthFileUploadStubs } from '../helpers'; - -const SleuthImportWizardCreateMetaAnalyses: React.FC<{ - projectId: string; - studysetId: string; - annotationId: string; - sleuthImports: ISleuthFileUploadStubs[]; -}> = ({ projectId, studysetId, annotationId, sleuthImports }) => { - const { isLoading: getProjectIsLoading, isError: getProjectIsError } = useGetProjectById(projectId); - const { createMetaAnalysis } = useCreateAlgorithmSpecification(); - const [buttonIsLoading, setButtonIsLoading] = useState(false); - const { enqueueSnackbar } = useSnackbar(); - const navigate = useNavigate(); - const [shouldCreateMetaAnalyses, setShouldCreateMetaAnalyses] = useState(); - const queryClient = useQueryClient(); - const [selectedMetaAnalysisAlgorithm, setSelectedMetaAnalysisAlgorithm] = useState({ - estimator: null, - corrector: null, - estimatorArgs: {}, - correctorArgs: {}, - }); - - const handleCreateMetaAnalysisDetails = async () => { - if (!shouldCreateMetaAnalyses) { - navigate(`/projects/${projectId}/meta-analyses`); - return; - } else { - setButtonIsLoading(true); - for (const sleuthImport of sleuthImports) { - // Later on, the library HandsOnTable will be used to render the annotaiton in a spreadsheet like UI. - // We want to use the filename as a key, but we cannot include periods due to this issue: - // https://github.com/handsontable/handsontable/issues/5439 - // - // As a result, we should remove the period from the filename - const filenameReplacePeriodsWithUnderscores = sleuthImport.fileName.replaceAll('.', '_'); - - const { weights, conditions, databaseStudyset } = getWeightAndConditionsForSpecification( - selectedMetaAnalysisAlgorithm.estimator, - { - selectionKey: filenameReplacePeriodsWithUnderscores, - type: EPropertyType.BOOLEAN, - selectionValue: true, - referenceDataset: undefined, - } - ); - - await createMetaAnalysis( - projectId, - EAnalysisType.CBMA, - selectedMetaAnalysisAlgorithm!.estimator, - selectedMetaAnalysisAlgorithm!.corrector, - studysetId, - annotationId, - filenameReplacePeriodsWithUnderscores, - `Untitled sleuth project: ${ - selectedMetaAnalysisAlgorithm!.estimator?.label - } Meta Analysis: ${sleuthImport.fileName}`, - `${selectedMetaAnalysisAlgorithm!.estimator?.label} Meta Analysis`, - selectedMetaAnalysisAlgorithm!.estimatorArgs, - selectedMetaAnalysisAlgorithm!.correctorArgs, - conditions, - weights, - databaseStudyset - ); - } - - await queryClient.invalidateQueries({ - queryKey: ['projects'], - }); - - setButtonIsLoading(false); - enqueueSnackbar('Meta-analyses created', { variant: 'success' }); - navigate(`/projects/${projectId}/meta-analyses`); - return; - } - }; - - const nextButtonDisabled = useMemo(() => { - if (shouldCreateMetaAnalyses === undefined || shouldCreateMetaAnalyses === null) { - return true; - } else if (shouldCreateMetaAnalyses === false) { - return false; - } else { - return !selectedMetaAnalysisAlgorithm.estimator; - } - }, [selectedMetaAnalysisAlgorithm, shouldCreateMetaAnalyses]); - - const algorithmOptions = useMemo(() => { - // later, we may want to add more basic options. For now, just these two - return metaAnalyticAlgorithms.filter((x) => x.label === 'MKDADensity' || x.label === 'ALE'); - }, []); - - return ( - - - - Would you like to create a meta-analysis for each file you've uploaded? - - - This will automatically create a new meta-analysis specification for each separate file assuming - they are distinct sets of coordinates. -
- This step is optional. You can skip this and create a custom meta-analysis later. -
- - { - setShouldCreateMetaAnalyses(value); - if (!value) { - return setSelectedMetaAnalysisAlgorithm({ - estimator: null, - corrector: null, - estimatorArgs: {}, - correctorArgs: {}, - }); - } - }} - aria-label="Platform" - > - Yes - No - - -
- {shouldCreateMetaAnalyses && ( - - Which algorithm would you like to use? - - Default parameters will be used. If you are unsure, we suggest starting with MKDADensity. To - replicate GingerALE, select ALE. - - - { - const value = e.target.value; - const correspondingOption = algorithmOptions.find((x) => x.label === value); - setSelectedMetaAnalysisAlgorithm({ - estimator: correspondingOption as IAutocompleteObject, - estimatorArgs: getDefaultValuesForTypeAndParameter(EAnalysisType.CBMA, value), - corrector: null, - correctorArgs: {}, - }); - }} - value={selectedMetaAnalysisAlgorithm?.estimator?.label || null} - > - {algorithmOptions.map((algorithmOption) => ( - } - key={algorithmOption.label} - label={ - - {algorithmOption.label} - {algorithmOption.description} - - } - /> - ))} - - - - )} - - - - - -
-
- ); -}; - -export default SleuthImportWizardCreateMetaAnalyses; diff --git a/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardIntroduction.tsx b/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardIntroduction.tsx deleted file mode 100644 index 6918c7826..000000000 --- a/compose/neurosynth-frontend/src/pages/SleuthImport/components/SleuthImportWizardIntroduction.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { Box, Button, Typography } from '@mui/material'; -import CurationImportBaseStyles from 'pages/CurationImport/components/CurationImport.styles'; - -const SleuthImportWizardIntroduction: React.FC<{ onNext: () => void }> = (props) => { - const { onNext } = props; - return ( - - - - This is a step by step interface for creating a project from a sleuth file. - - - Once a sleuth file is uploaded in the next page, neurosynth compose will try and map the studies - within the sleuth file to existing studies. - - - Neurosynth compose will treat each file uploaded as a separate meta-analysis. Upload multiple sleuth - files to create multiple runnable meta-analyses in neurosynth compose. - - - The next step will guide you through the upload process. On the next page, click on the question - mark icon at the top of the page to see the correct, expected format for sleuth files. - - Click next to get started. - - - - - - - - ); -}; - -export default SleuthImportWizardIntroduction;