From de0d47dae431d2a1ba718c84816d339e3de8ef91 Mon Sep 17 00:00:00 2001 From: James Kent Date: Tue, 19 Sep 2023 05:45:16 -0500 Subject: [PATCH 1/2] [ENH] add username and neurostore studyset script (#590) * add username script * add script to create neurostore_studyset --- compose/scripts/add_usernames.py | 19 +++++++++++++ store/scripts/create_neurostore_studyset.py | 31 +++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 compose/scripts/add_usernames.py create mode 100644 store/scripts/create_neurostore_studyset.py diff --git a/compose/scripts/add_usernames.py b/compose/scripts/add_usernames.py new file mode 100644 index 000000000..9f46190e1 --- /dev/null +++ b/compose/scripts/add_usernames.py @@ -0,0 +1,19 @@ +from auth0.v3.management.users import Users +from neurosynth_compose.resources.users import User + +TOKEN = "INSERT TOKEN" + + +user_endpoint = Users(domain="neurosynth-staging.us.auth0.com", token=TOKEN) + +result = user_endpoint.list(per_page=100)['users'] + +sql_users = [] +for user in result: + print(user['name']) + sql_user = User.query.filter_by(external_id=user['user_id']).one_or_none() + if sql_user is None: + sql_user = User(external_id=user['user_id']) + sql_user.name = user['name'] + sql_users.append(sql_user) + print(user['user_id']) diff --git a/store/scripts/create_neurostore_studyset.py b/store/scripts/create_neurostore_studyset.py new file mode 100644 index 000000000..f65a0626d --- /dev/null +++ b/store/scripts/create_neurostore_studyset.py @@ -0,0 +1,31 @@ +from neurostore.models import BaseStudy, Studyset +from sqlalchemy.orm import joinedload + + +base_studies = BaseStudy.query.options( + joinedload("versions") +).filter_by(has_coordinates=True).all() + +neurostore_studyset = [] +for bs in base_studies: + if not bs.versions or not bs.has_coordinates: + continue + selected_study = bs.versions[0] + + for v in bs.versions[1:]: + if not v.has_coordinates: + continue + + if v.user is not None: + if selected_study.user is None: + selected_study = v + else: + if ( + selected_study.updated_at or selected_study.created_at + ) <= (v.updated_at or v.created_at): + selected_study = v + neurostore_studyset.append(selected_study) + +ss = Studyset(name="Neurostore Studyset", description="aggregation of studies on the neurostore database. Ran periodically, may not represent the latest state of the database.", studies=neurostore_studyset) + + From 76ec881b40181595cf53baee81ce901c44b415dc Mon Sep 17 00:00:00 2001 From: Nicholas Lee Date: Thu, 21 Sep 2023 14:38:45 -0400 Subject: [PATCH 2/2] 197 display user icon name (#591) * feat: add user profile and usernames * feat: changed updated usernames, and fixed studysets page, better base studies support * fix: removed unnecessary imports and updated openapi spec, fixed failing tests * fix: resolve failing cypress test * fix: added fixture to fix failing cypress test * fix: update cypress to v12 due to issue with chrome 117 --- .../cypress/e2e/pages/EditStudyPage.cy.tsx | 6 +- .../e2e/pages/PublicStudiesPage.cy.tsx | 2 +- .../cypress/e2e/pages/StudyPage.cy.tsx | 10 +- .../cypress/fixtures/project.json | 47 +++++++ compose/neurosynth-frontend/package-lock.json | 71 ++++++----- compose/neurosynth-frontend/package.json | 2 +- .../ImportFromNeurostore/NeurostoreSearch.tsx | 7 +- .../EditAnnotations/EditAnnotations.tsx | 36 +++--- .../components/Navbar/NavDrawer/NavDrawer.tsx | 2 +- .../Navbar/NavToolbar/NavToolbar.spec.tsx | 6 +- .../Navbar/NavToolbar/NavToolbar.tsx | 25 ++-- .../src/components/Navbar/Navbar.tsx | 6 +- .../NeurosynthAvatar/NeurosynthAvatar.tsx | 73 +++++++++++ .../components/Search/SearchBar/SearchBar.tsx | 87 +++++++------ .../SearchContainer/SearchContainer.tsx | 8 +- .../src/hooks/studysets/useGetStudysets.tsx | 6 +- compose/neurosynth-frontend/src/index.tsx | 16 +-- .../src/neurosynth-compose-typescript-sdk | 2 +- .../AnnotationsPage/AnnotationsPage.tsx | 87 +++++++------ .../pages/BaseNavigation/BaseNavigation.tsx | 63 +++++++--- .../src/pages/LandingPage/LandingPage.tsx | 2 +- .../MetaAnalysesPage/MetaAnalysesPage.tsx | 8 +- .../Studies/BaseStudyPage/BaseStudyPage.tsx | 101 +++++++++++++++ .../pages/Studies/StudiesPage/StudiesPage.tsx | 13 +- .../src/pages/Studies/StudiesPage/models.ts | 8 +- .../src/pages/Studies/StudyPage/StudyPage.tsx | 118 ++++++------------ .../Studysets/StudysetPage/StudysetPage.tsx | 45 +------ .../Studysets/StudysetsPage/StudysetsPage.tsx | 18 +-- .../pages/UserProfilePage/UserProfilePage.tsx | 59 +++++++++ compose/neurosynth_compose/openapi | 2 +- store/neurostore/openapi | 2 +- 31 files changed, 599 insertions(+), 339 deletions(-) create mode 100644 compose/neurosynth-frontend/cypress/fixtures/project.json create mode 100644 compose/neurosynth-frontend/src/components/Navbar/NeurosynthAvatar/NeurosynthAvatar.tsx create mode 100644 compose/neurosynth-frontend/src/pages/Studies/BaseStudyPage/BaseStudyPage.tsx create mode 100644 compose/neurosynth-frontend/src/pages/UserProfilePage/UserProfilePage.tsx diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/EditStudyPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/EditStudyPage.cy.tsx index 8b38b2ee0..1116c232e 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/EditStudyPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/EditStudyPage.cy.tsx @@ -2,7 +2,7 @@ export {}; -const PATH = '/studies/mock-study-id/edit'; +const PATH = '/projects/abc123/extraction/studies/mock-study-id/edit'; const PAGE_NAME = 'EditStudyPage'; describe(PAGE_NAME, () => { @@ -12,11 +12,11 @@ describe(PAGE_NAME, () => { }); it('should load', () => { - cy.intercept('GET', `**/api/projects*`).as('realProjectsRequest'); + cy.intercept('GET', `**/api/projects/*`, { fixture: 'project' }).as('projectFixture'); cy.intercept('GET', `**/api/studies/mock-study-id*`, { fixture: 'study' }).as( 'studyFixture' ); - cy.visit(PATH).wait('@studyFixture'); + cy.visit(PATH).wait('@projectFixture').wait('@studyFixture'); // cy.login('mocked').wait('@realProjectsRequest').visit(PATH).wait('@studyFixture'); }); diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/PublicStudiesPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/PublicStudiesPage.cy.tsx index 755069242..f03323925 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/PublicStudiesPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/PublicStudiesPage.cy.tsx @@ -4,7 +4,7 @@ import { mockStudies } from 'testing/mockData'; export {}; -const PATH = '/studies'; +const PATH = '/base-studies'; const PAGE_NAME = 'StudiesPage'; describe(PAGE_NAME, () => { diff --git a/compose/neurosynth-frontend/cypress/e2e/pages/StudyPage.cy.tsx b/compose/neurosynth-frontend/cypress/e2e/pages/StudyPage.cy.tsx index 1b6364717..53fe491b4 100644 --- a/compose/neurosynth-frontend/cypress/e2e/pages/StudyPage.cy.tsx +++ b/compose/neurosynth-frontend/cypress/e2e/pages/StudyPage.cy.tsx @@ -22,14 +22,8 @@ describe(PAGE_NAME, () => { cy.intercept('GET', `**/api/studies/**`, { fixture: 'study', }).as('studyFixture'); - cy.intercept('GET', `**/api/base-studies/**`, { - fixture: 'baseStudy', - }).as('baseStudyFixture'); - - cy.visit(PATH) - .wait('@semanticScholarFixture') - .wait('@studyFixture') - .wait('@baseStudyFixture'); + + cy.visit(PATH).wait('@semanticScholarFixture').wait('@studyFixture'); // .get('tr') // .eq(2) // .click() diff --git a/compose/neurosynth-frontend/cypress/fixtures/project.json b/compose/neurosynth-frontend/cypress/fixtures/project.json new file mode 100644 index 000000000..82ba1094a --- /dev/null +++ b/compose/neurosynth-frontend/cypress/fixtures/project.json @@ -0,0 +1,47 @@ +{ + "created_at": "2023-09-14T18:04:28.079423+00:00", + "description": "", + "id": "4HxF4g4WC5zj", + "meta_analyses": [], + "name": "My project name", + "neurostore_study": { + "created_at": "2023-09-14T18:04:28.090521+00:00", + "exception": null, + "neurostore_id": "4PG9x8gYaoWd", + "status": "PENDING", + "traceback": null, + "updated_at": "2023-09-14T18:04:28.111166+00:00" + }, + "neurostore_url": "https://neurostore.org/api/studies/4PG9x8gYaoWd", + "provenance": { + "curationMetadata": { + "columns": [], + "exclusionTags": [], + "identificationSources": [], + "infoTags": [], + "prismaConfig": { + "eligibility": { + "exclusionTags": [] + }, + "identification": { + "exclusionTags": [] + }, + "isPrisma": false, + "screening": { + "exclusionTags": [] + } + } + }, + "extractionMetadata": { + "studyStatusList": [] + }, + "metaAnalysisMetadata": { + "canEditMetaAnalyses": false + } + }, + "public": false, + "updated_at": "2023-09-14T18:04:34.184976+00:00", + "user": "github|12387943", + "username": "Nicholas Lee" + } + \ No newline at end of file diff --git a/compose/neurosynth-frontend/package-lock.json b/compose/neurosynth-frontend/package-lock.json index 3983a2034..2e6f0beb1 100644 --- a/compose/neurosynth-frontend/package-lock.json +++ b/compose/neurosynth-frontend/package-lock.json @@ -56,7 +56,7 @@ "@testing-library/user-event": "^12.8.3", "@types/body-scroll-lock": "^3.1.0", "@types/node": "^18.6.1", - "cypress": "^10.3.1", + "cypress": "^12.15.0", "env-cmd": "^10.1.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-cypress": "^2.12.1", @@ -9689,9 +9689,9 @@ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, "node_modules/cypress": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.3.1.tgz", - "integrity": "sha512-As9HrExjAgpgjCnbiQCuPdw5sWKx5HUJcK2EOKziu642akwufr/GUeqL5UnCPYXTyyibvEdWT/pSC2qnGW/e5w==", + "version": "12.15.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.15.0.tgz", + "integrity": "sha512-FqGbxsH+QgjStuTO9onXMIeF44eOrgVwPvlcvuzLIaePQMkl72YgBvpuHlBGRcrw3Q4SvqKfajN8iV5XWShAiQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -9709,12 +9709,12 @@ "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", - "commander": "^5.1.0", + "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", - "debug": "^4.3.2", + "debug": "^4.3.4", "enquirer": "^2.3.6", - "eventemitter2": "^6.4.3", + "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", @@ -9727,7 +9727,7 @@ "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", "proxy-from-env": "1.0.0", @@ -9742,7 +9742,7 @@ "cypress": "bin/cypress" }, "engines": { - "node": ">=12.0.0" + "node": "^14.0.0 || ^16.0.0 || >=18.0.0" } }, "node_modules/cypress/node_modules/@types/node": { @@ -9813,9 +9813,9 @@ "dev": true }, "node_modules/cypress/node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true, "engines": { "node": ">= 6" @@ -11447,9 +11447,9 @@ } }, "node_modules/eventemitter2": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.5.tgz", - "integrity": "sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==", + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", "dev": true }, "node_modules/eventemitter3": { @@ -17687,9 +17687,12 @@ } }, "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/mkdirp": { "version": "0.5.6", @@ -30856,9 +30859,9 @@ "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" }, "cypress": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-10.3.1.tgz", - "integrity": "sha512-As9HrExjAgpgjCnbiQCuPdw5sWKx5HUJcK2EOKziu642akwufr/GUeqL5UnCPYXTyyibvEdWT/pSC2qnGW/e5w==", + "version": "12.15.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.15.0.tgz", + "integrity": "sha512-FqGbxsH+QgjStuTO9onXMIeF44eOrgVwPvlcvuzLIaePQMkl72YgBvpuHlBGRcrw3Q4SvqKfajN8iV5XWShAiQ==", "dev": true, "requires": { "@cypress/request": "^2.88.10", @@ -30875,12 +30878,12 @@ "check-more-types": "^2.24.0", "cli-cursor": "^3.1.0", "cli-table3": "~0.6.1", - "commander": "^5.1.0", + "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", - "debug": "^4.3.2", + "debug": "^4.3.4", "enquirer": "^2.3.6", - "eventemitter2": "^6.4.3", + "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", @@ -30893,7 +30896,7 @@ "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", - "minimist": "^1.2.6", + "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", "proxy-from-env": "1.0.0", @@ -30957,9 +30960,9 @@ "dev": true }, "commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true }, "execa": { @@ -32159,9 +32162,9 @@ } }, "eventemitter2": { - "version": "6.4.5", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.5.tgz", - "integrity": "sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==", + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", "dev": true }, "eventemitter3": { @@ -36790,9 +36793,9 @@ } }, "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "mkdirp": { "version": "0.5.6", diff --git a/compose/neurosynth-frontend/package.json b/compose/neurosynth-frontend/package.json index c3494aee1..65db879fa 100644 --- a/compose/neurosynth-frontend/package.json +++ b/compose/neurosynth-frontend/package.json @@ -90,7 +90,7 @@ "@testing-library/user-event": "^12.8.3", "@types/body-scroll-lock": "^3.1.0", "@types/node": "^18.6.1", - "cypress": "^10.3.1", + "cypress": "^12.15.0", "env-cmd": "^10.1.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-cypress": "^2.12.1", diff --git a/compose/neurosynth-frontend/src/components/CurationComponents/CurationImport/CurationImport/ImportFromNeurostore/NeurostoreSearch.tsx b/compose/neurosynth-frontend/src/components/CurationComponents/CurationImport/CurationImport/ImportFromNeurostore/NeurostoreSearch.tsx index 0ecc4f3ee..a239af89d 100644 --- a/compose/neurosynth-frontend/src/components/CurationComponents/CurationImport/CurationImport/ImportFromNeurostore/NeurostoreSearch.tsx +++ b/compose/neurosynth-frontend/src/components/CurationComponents/CurationImport/CurationImport/ImportFromNeurostore/NeurostoreSearch.tsx @@ -190,7 +190,7 @@ const NeurostoreSearch: React.FC = (props) => { data-tour={index === 0 ? 'StudiesPage-4' : null} sx={NeurosynthTableStyles.tableRow} key={studyrow.id || index} - onClick={() => history.push(`/studies/${studyrow.id}`)} + onClick={() => history.push(`/base-studies/${studyrow.id}`)} > {studyrow?.name || ( @@ -207,10 +207,7 @@ const NeurostoreSearch: React.FC = (props) => { No Journal )} - - {(studyrow?.user === user?.sub ? 'Me' : studyrow?.user) || - 'Neurosynth-Compose'} - + {studyrow?.username || 'Neurosynth-Compose'} ))} /> diff --git a/compose/neurosynth-frontend/src/components/EditAnnotations/EditAnnotations.tsx b/compose/neurosynth-frontend/src/components/EditAnnotations/EditAnnotations.tsx index 1f943199c..9099f416d 100644 --- a/compose/neurosynth-frontend/src/components/EditAnnotations/EditAnnotations.tsx +++ b/compose/neurosynth-frontend/src/components/EditAnnotations/EditAnnotations.tsx @@ -4,11 +4,6 @@ import { DetailedSettings as MergeCellsSettings } from 'handsontable/plugins/mer import { ColumnSettings } from 'handsontable/settings'; import { useGetAnnotationById, useUpdateAnnotationById } from 'hooks'; import { NoteCollectionReturn } from 'neurostore-typescript-sdk'; -import { - useInitProjectStoreIfRequired, - useProjectExtractionAnnotationId, -} from 'pages/Projects/ProjectPage/ProjectStore'; -import { useInitStudyStoreIfRequired } from 'pages/Studies/StudyStore'; import { useCallback, useEffect, useRef, useState } from 'react'; import AnnotationsHotTable from './AnnotationsHotTable/AnnotationsHotTable'; import { @@ -21,17 +16,24 @@ import { noteKeyArrToObj, noteKeyObjToArr, } from './helpers/utils'; +import { useAuth0 } from '@auth0/auth0-react'; +import { useSnackbar } from 'notistack'; const hardCodedColumns = ['Study', 'Analysis']; -const EditAnnotations: React.FC = (props) => { - const annotationId = useProjectExtractionAnnotationId(); - - useInitProjectStoreIfRequired(); - useInitStudyStoreIfRequired(); +const EditAnnotations: React.FC<{ annotationId: string }> = (props) => { + const { user } = useAuth0(); + const { enqueueSnackbar } = useSnackbar(); + const { mutate, isLoading: updateAnnotationIsLoading } = useUpdateAnnotationById( + props.annotationId + ); + const { + data, + isLoading: getAnnotationIsLoading, + isError, + } = useGetAnnotationById(props.annotationId); - const { mutate, isLoading: updateAnnotationIsLoading } = useUpdateAnnotationById(annotationId); - const { data, isLoading: getAnnotationIsLoading, isError } = useGetAnnotationById(annotationId); + const theLoggedInUserOwnsThisAnnotation = (user?.sub || null) === (data?.user || undefined); // tracks the changes made to hot table const hotTableDataUpdatesRef = useRef<{ @@ -90,7 +92,13 @@ const EditAnnotations: React.FC = (props) => { }, [data]); const handleClickSave = () => { - if (!annotationId) return; + if (!props.annotationId) return; + if (!theLoggedInUserOwnsThisAnnotation) { + enqueueSnackbar('You do not have permission to edit this annotation', { + variant: 'error', + }); + return; + } const { hotData, noteKeys } = hotTableDataUpdatesRef.current; @@ -103,7 +111,7 @@ const EditAnnotations: React.FC = (props) => { mutate( { - argAnnotationId: annotationId, + argAnnotationId: props.annotationId, annotation: { notes: updatedAnnotationNotes.map((annotationNote) => ({ note: annotationNote.note, diff --git a/compose/neurosynth-frontend/src/components/Navbar/NavDrawer/NavDrawer.tsx b/compose/neurosynth-frontend/src/components/Navbar/NavDrawer/NavDrawer.tsx index 72b1e0f2f..43cad1a80 100644 --- a/compose/neurosynth-frontend/src/components/Navbar/NavDrawer/NavDrawer.tsx +++ b/compose/neurosynth-frontend/src/components/Navbar/NavDrawer/NavDrawer.tsx @@ -89,7 +89,7 @@ const NavDrawer: React.FC = (props) => { - history.push('/studies')}> + history.push('/base-studies')}> diff --git a/compose/neurosynth-frontend/src/components/Navbar/NavToolbar/NavToolbar.spec.tsx b/compose/neurosynth-frontend/src/components/Navbar/NavToolbar/NavToolbar.spec.tsx index 519e867b3..7340436b9 100644 --- a/compose/neurosynth-frontend/src/components/Navbar/NavToolbar/NavToolbar.spec.tsx +++ b/compose/neurosynth-frontend/src/components/Navbar/NavToolbar/NavToolbar.spec.tsx @@ -42,7 +42,7 @@ describe('NavToolbar Component', () => { expect(screen.queryByText('new project')).not.toBeInTheDocument(); expect(screen.queryByText('my projects')).not.toBeInTheDocument(); - expect(screen.queryByText('LOGOUT')).not.toBeInTheDocument(); + expect(screen.queryByTestId('PersonIcon')).not.toBeInTheDocument(); expect(screen.queryByText('explore')).toBeInTheDocument(); expect(screen.queryByText('HELP')).toBeInTheDocument(); @@ -66,7 +66,7 @@ describe('NavToolbar Component', () => { expect(screen.queryByText('my projects')).toBeInTheDocument(); expect(screen.queryByText('explore')).toBeInTheDocument(); expect(screen.queryByText('HELP')).toBeInTheDocument(); - expect(screen.queryByText('LOGOUT')).toBeInTheDocument(); + expect(screen.getByTestId('PersonIcon')).toBeInTheDocument(); }); it('should login', () => { @@ -97,6 +97,8 @@ describe('NavToolbar Component', () => { ); + // open popup + userEvent.click(screen.getByTestId('PersonIcon')); userEvent.click(screen.getByText('LOGOUT')); expect(mockLogout).toHaveBeenCalled(); }); diff --git a/compose/neurosynth-frontend/src/components/Navbar/NavToolbar/NavToolbar.tsx b/compose/neurosynth-frontend/src/components/Navbar/NavToolbar/NavToolbar.tsx index 6cc1eb054..cf4968c22 100644 --- a/compose/neurosynth-frontend/src/components/Navbar/NavToolbar/NavToolbar.tsx +++ b/compose/neurosynth-frontend/src/components/Navbar/NavToolbar/NavToolbar.tsx @@ -1,15 +1,16 @@ import { useAuth0 } from '@auth0/auth0-react'; -import { Box, Button, Typography, Toolbar } from '@mui/material'; -import NavbarStyles from '../Navbar.styles'; import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; -import OpenInNewIcon from '@mui/icons-material/OpenInNew'; -import { NavLink, useHistory } from 'react-router-dom'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import { Box, Button, Toolbar, Typography } from '@mui/material'; import CreateDetailsDialog from 'components/Dialogs/CreateDetailsDialog/CreateDetailsDialog'; -import NavToolbarStyles from './NavToolbar.styles'; import NavToolbarPopupSubMenu from 'components/Navbar/NavSubMenu/NavToolbarPopupSubMenu'; import { useState } from 'react'; +import { NavLink, useHistory } from 'react-router-dom'; import { INav } from '../Navbar'; +import NavbarStyles from '../Navbar.styles'; +import NeurosynthAvatar from 'components/Navbar/NeurosynthAvatar/NeurosynthAvatar'; +import NavToolbarStyles from './NavToolbar.styles'; const NavToolbar: React.FC = (props) => { const { isAuthenticated } = useAuth0(); @@ -74,7 +75,7 @@ const NavToolbar: React.FC = (props) => { options={[ { label: 'STUDIES', - onClick: () => history.push('/studies'), + onClick: () => history.push('/base-studies'), }, { label: 'STUDYSETS', @@ -100,17 +101,7 @@ const NavToolbar: React.FC = (props) => { HELP - + diff --git a/compose/neurosynth-frontend/src/components/Navbar/Navbar.tsx b/compose/neurosynth-frontend/src/components/Navbar/Navbar.tsx index bb3f2114c..9c9be114b 100644 --- a/compose/neurosynth-frontend/src/components/Navbar/Navbar.tsx +++ b/compose/neurosynth-frontend/src/components/Navbar/Navbar.tsx @@ -14,6 +14,7 @@ export interface INav { } export const NAVBAR_HEIGHT = 64; +const AUTH0_AUDIENCE = process.env.REACT_APP_AUTH0_AUDIENCE; const Navbar: React.FC = (_props) => { const { loginWithPopup, logout } = useAuth0(); @@ -21,7 +22,10 @@ const Navbar: React.FC = (_props) => { const history = useHistory(); const handleLogin = async () => { - await loginWithPopup(); + await loginWithPopup({ + audience: AUTH0_AUDIENCE, + scope: 'openid profile email offline_access', + }); history.push('/'); }; diff --git a/compose/neurosynth-frontend/src/components/Navbar/NeurosynthAvatar/NeurosynthAvatar.tsx b/compose/neurosynth-frontend/src/components/Navbar/NeurosynthAvatar/NeurosynthAvatar.tsx new file mode 100644 index 000000000..5fdfe56a8 --- /dev/null +++ b/compose/neurosynth-frontend/src/components/Navbar/NeurosynthAvatar/NeurosynthAvatar.tsx @@ -0,0 +1,73 @@ +import { useAuth0 } from '@auth0/auth0-react'; +import { + Avatar, + Button, + IconButton, + ListItem, + ListItemButton, + ListItemText, + Menu, +} from '@mui/material'; +import { useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import NavToolbarStyles from 'components/Navbar/NavToolbar/NavToolbar.styles'; + +const NeurosynthAvatar: React.FC<{ onLogin: () => void; onLogout: () => void }> = (props) => { + const history = useHistory(); + const { user, isAuthenticated } = useAuth0(); + const [anchorEl, setAnchorEl] = useState(null); + + const handleOpenAvatarMenu = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseAvatarMenu = () => { + setAnchorEl(null); + }; + + const handleDirectToUserProfile = () => { + history.push('/user-profile'); + handleCloseAvatarMenu(); + }; + + return ( + <> + {isAuthenticated ? ( + <> + + + + + + + PROFILE + + + + + LOGOUT + + + + + ) : ( + + )} + + ); +}; + +export default NeurosynthAvatar; diff --git a/compose/neurosynth-frontend/src/components/Search/SearchBar/SearchBar.tsx b/compose/neurosynth-frontend/src/components/Search/SearchBar/SearchBar.tsx index 3e00cc99d..d3931fc58 100644 --- a/compose/neurosynth-frontend/src/components/Search/SearchBar/SearchBar.tsx +++ b/compose/neurosynth-frontend/src/components/Search/SearchBar/SearchBar.tsx @@ -19,6 +19,7 @@ import SearchSelectSortChip from './SearchBarAccessories/SearchSelectSortChip'; export interface ISearchBar { onSearch: (searchArgs: Partial) => void; searchButtonColor?: string; + searchMode?: 'study-search' | 'studyset-search'; } const searchPlaceholderExamples = [ @@ -30,15 +31,15 @@ const searchPlaceholderExamples = [ ]; const SearchBar: React.FC = (props) => { - const { onSearch, searchButtonColor = 'primary' } = props; + const { onSearch, searchButtonColor = 'primary', searchMode = 'study-search' } = props; const [placeholder, setPlaceholder] = useState(searchPlaceholderExamples[0]); const location = useLocation(); const [searchState, setSearchState] = useState>({ genericSearchStr: '', // this defaults to undefined if empty in useGetBaseStudies - dataType: SearchDataType.COORDINATE, + dataType: SearchDataType.ALL, source: Source.ALL, - sortBy: SortBy.TITLE, + sortBy: SortBy.RELEVANCE, descOrder: true, nameSearch: '', // this defaults to undefined if empty in useGetBaseStudies descriptionSearch: '', // this defaults to undefined if empty in useGetBaseStudies @@ -48,10 +49,16 @@ const SearchBar: React.FC = (props) => { // set new placeholder on reload useEffect(() => { - const placeholder = - searchPlaceholderExamples[Math.floor(Math.random() * searchPlaceholderExamples.length)]; - setPlaceholder(placeholder); - }, []); + if (searchMode === 'study-search') { + const placeholder = + searchPlaceholderExamples[ + Math.floor(Math.random() * searchPlaceholderExamples.length) + ]; + setPlaceholder(placeholder); + } else { + setPlaceholder('Enter a studyset name...'); + } + }, [searchMode]); useEffect(() => { const searchCriteria = getSearchCriteriaFromURL(location.search); @@ -59,7 +66,7 @@ const SearchBar: React.FC = (props) => { genericSearchStr: searchCriteria.genericSearchStr || '', dataType: searchCriteria.dataType || SearchDataType.COORDINATE, source: searchCriteria.source || Source.ALL, - sortBy: searchCriteria.sortBy || SortBy.TITLE, + sortBy: searchCriteria.sortBy || SortBy.RELEVANCE, descOrder: searchCriteria.descOrder, nameSearch: searchCriteria.nameSearch || '', descriptionSearch: searchCriteria.descriptionSearch || '', @@ -73,7 +80,7 @@ const SearchBar: React.FC = (props) => { genericSearchStr: '', dataType: SearchDataType.ALL, source: Source.ALL, - sortBy: SortBy.TITLE, + sortBy: SortBy.RELEVANCE, descOrder: true, nameSearch: '', descriptionSearch: '', @@ -143,44 +150,46 @@ const SearchBar: React.FC = (props) => { borderBottomLeftRadius: '0px', borderLeft: '0px !important', width: '100px', + color: searchButtonColor, }} disableElevation - color="primary" variant="text" > Reset - - - onSearch({ ...searchState, dataType: selectedDataType }) - } - options={[ - { value: SearchDataType.ALL, label: 'All' }, - { value: SearchDataType.COORDINATE, label: 'Coordinates' }, - { value: SearchDataType.IMAGE, label: 'Images' }, - ]} - /> - - onSearch({ ...searchState, descOrder }) - } - onSelectSort={(sortBy) => onSearch({ ...searchState, sortBy })} - /> - - + {searchMode === 'study-search' && ( + + + onSearch({ ...searchState, dataType: selectedDataType }) + } + options={[ + { value: SearchDataType.ALL, label: 'All' }, + { value: SearchDataType.COORDINATE, label: 'Coordinates' }, + { value: SearchDataType.IMAGE, label: 'Images' }, + ]} + /> + + onSearch({ ...searchState, descOrder }) + } + onSelectSort={(sortBy) => onSearch({ ...searchState, sortBy })} + /> + + + )} diff --git a/compose/neurosynth-frontend/src/components/Search/SearchContainer/SearchContainer.tsx b/compose/neurosynth-frontend/src/components/Search/SearchContainer/SearchContainer.tsx index 606854268..e1a4dba5b 100644 --- a/compose/neurosynth-frontend/src/components/Search/SearchContainer/SearchContainer.tsx +++ b/compose/neurosynth-frontend/src/components/Search/SearchContainer/SearchContainer.tsx @@ -14,6 +14,7 @@ export interface ISearchContainer { pageOfResults: number; searchButtonColor?: string; paginationSelectorStyles?: Style; + searchMode?: 'study-search' | 'studyset-search'; } const getNumTotalPages = (totalCount: number | undefined, pageSize: number | undefined) => { @@ -36,6 +37,7 @@ const SearchContainer: React.FC = (props) => { children, searchButtonColor = 'primary', paginationSelectorStyles = {}, + searchMode = 'study-search', } = props; const handleRowsPerPageChange = ( @@ -52,7 +54,11 @@ const SearchContainer: React.FC = (props) => { return ( <> - + { return API.NeurostoreServices.StudySetsService.studysetsGet( - searchCriteria.genericSearchStr, + undefined, searchCriteria.sortBy, searchCriteria.pageOfResults, searchCriteria.descOrder, searchCriteria.pageSize, searchCriteria.isNested, - searchCriteria.nameSearch, - searchCriteria.descriptionSearch, + searchCriteria.genericSearchStr, + undefined, undefined, searchCriteria.showUnique, searchCriteria.source === Source.ALL ? undefined : searchCriteria.source, diff --git a/compose/neurosynth-frontend/src/index.tsx b/compose/neurosynth-frontend/src/index.tsx index c464995d7..5b10eb033 100644 --- a/compose/neurosynth-frontend/src/index.tsx +++ b/compose/neurosynth-frontend/src/index.tsx @@ -1,13 +1,13 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; import { Auth0Provider } from '@auth0/auth0-react'; import { grey } from '@mui/material/colors'; +import { createTheme, ThemeProvider } from '@mui/material/styles'; import { SystemStyleObject } from '@mui/system'; -import { BrowserRouter } from 'react-router-dom'; import * as Sentry from '@sentry/react'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './index.css'; export type Style = Record; export type ColorOptions = @@ -107,10 +107,12 @@ ReactDOM.render( diff --git a/compose/neurosynth-frontend/src/neurosynth-compose-typescript-sdk b/compose/neurosynth-frontend/src/neurosynth-compose-typescript-sdk index 774a5fd7b..9f567ca59 160000 --- a/compose/neurosynth-frontend/src/neurosynth-compose-typescript-sdk +++ b/compose/neurosynth-frontend/src/neurosynth-compose-typescript-sdk @@ -1 +1 @@ -Subproject commit 774a5fd7b93a1f4cd08d160d5c630f867091fab3 +Subproject commit 9f567ca597ca6a6d6ed9fa9321c72ce985135ac1 diff --git a/compose/neurosynth-frontend/src/pages/Annotations/AnnotationsPage/AnnotationsPage.tsx b/compose/neurosynth-frontend/src/pages/Annotations/AnnotationsPage/AnnotationsPage.tsx index 34554e49c..79e350fa3 100644 --- a/compose/neurosynth-frontend/src/pages/Annotations/AnnotationsPage/AnnotationsPage.tsx +++ b/compose/neurosynth-frontend/src/pages/Annotations/AnnotationsPage/AnnotationsPage.tsx @@ -1,44 +1,61 @@ -import { Box } from '@mui/material'; +import { Box, Typography } from '@mui/material'; import EditAnnotations from 'components/EditAnnotations/EditAnnotations'; import NeurosynthBreadcrumbs from 'components/NeurosynthBreadcrumbs/NeurosynthBreadcrumbs'; -import { useProjectId, useProjectName } from 'pages/Projects/ProjectPage/ProjectStore'; +import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; +import { useGetAnnotationById } from 'hooks'; +import { + useInitProjectStoreIfRequired, + useProjectName, +} from 'pages/Projects/ProjectPage/ProjectStore'; +import { useParams } from 'react-router-dom'; + +const AnnotationsPage: React.FC = () => { + const { projectId, annotationId }: { projectId: string; annotationId: string } = useParams(); + const { data, isLoading: getAnnotationIsLoading, isError } = useGetAnnotationById(annotationId); + + useInitProjectStoreIfRequired(); -const AnnotationsPage: React.FC = (props) => { - const projectId = useProjectId(); const projectName = useProjectName(); - return ( - - - - + const viewingThisPageFromProject = !!projectId; - - - + return ( + + + + {data?.name} + {data?.description || ''} + + {viewingThisPageFromProject && ( + + + + )} + + ); }; diff --git a/compose/neurosynth-frontend/src/pages/BaseNavigation/BaseNavigation.tsx b/compose/neurosynth-frontend/src/pages/BaseNavigation/BaseNavigation.tsx index 904e8fd61..28741358e 100644 --- a/compose/neurosynth-frontend/src/pages/BaseNavigation/BaseNavigation.tsx +++ b/compose/neurosynth-frontend/src/pages/BaseNavigation/BaseNavigation.tsx @@ -8,8 +8,10 @@ import NotFoundPage from 'pages/NotFound/NotFoundPage'; import ProjectPage from 'pages/Projects/ProjectPage/ProjectPage'; import ExtractionPage from 'pages/ExtractionPage/ExtractionPage'; import CurationImportPage from 'pages/CurationPage/CurationImportPage'; -import AnnotationsPage from 'pages/Annotations/AnnotationsPage/AnnotationsPage'; import ProjectStudyPage from 'pages/Studies/ProjectStudyPage/ProjectStudyPage'; +import UserProfilePage from 'pages/UserProfilePage/UserProfilePage'; +import AnnotationsPage from 'pages/Annotations/AnnotationsPage/AnnotationsPage'; +import BaseStudyPage from 'pages/Studies/BaseStudyPage/BaseStudyPage'; const StudysetPage = React.lazy(() => import('../Studysets/StudysetPage/StudysetPage')); const StudysetsPage = React.lazy(() => import('../Studysets/StudysetsPage/StudysetsPage')); @@ -46,7 +48,7 @@ const BaseNavigation: React.FC = (_props) => { } > - + @@ -54,6 +56,18 @@ const BaseNavigation: React.FC = (_props) => { + + + + + @@ -69,31 +83,31 @@ const BaseNavigation: React.FC = (_props) => { - + - + + + + + + - - - - - - + @@ -101,11 +115,6 @@ const BaseNavigation: React.FC = (_props) => { - - - - - @@ -116,11 +125,22 @@ const BaseNavigation: React.FC = (_props) => { - + + + + + + @@ -131,6 +151,11 @@ const BaseNavigation: React.FC = (_props) => { + + + + + diff --git a/compose/neurosynth-frontend/src/pages/LandingPage/LandingPage.tsx b/compose/neurosynth-frontend/src/pages/LandingPage/LandingPage.tsx index 008d4dda3..333f2b673 100644 --- a/compose/neurosynth-frontend/src/pages/LandingPage/LandingPage.tsx +++ b/compose/neurosynth-frontend/src/pages/LandingPage/LandingPage.tsx @@ -133,7 +133,7 @@ const LandingPage = () => { Get started by browsing various  studies diff --git a/compose/neurosynth-frontend/src/pages/MetaAnalyses/MetaAnalysesPage/MetaAnalysesPage.tsx b/compose/neurosynth-frontend/src/pages/MetaAnalyses/MetaAnalysesPage/MetaAnalysesPage.tsx index 28563c947..266d0a1bb 100644 --- a/compose/neurosynth-frontend/src/pages/MetaAnalyses/MetaAnalysesPage/MetaAnalysesPage.tsx +++ b/compose/neurosynth-frontend/src/pages/MetaAnalyses/MetaAnalysesPage/MetaAnalysesPage.tsx @@ -1,4 +1,3 @@ -import { useAuth0 } from '@auth0/auth0-react'; import { Box, TableCell, TableRow, Typography } from '@mui/material'; import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; import NeurosynthTable from 'components/Tables/NeurosynthTable/NeurosynthTable'; @@ -7,10 +6,8 @@ import { useGetMetaAnalysesByProjectId } from 'hooks'; import { useHistory } from 'react-router-dom'; const MetaAnalysesPage: React.FC = (props) => { - // const { startTour } = useGetTour('MetaAnalysesPage'); const history = useHistory(); const { data, isLoading, isError } = useGetMetaAnalysesByProjectId(); - const { user } = useAuth0(); return ( <> @@ -69,9 +66,8 @@ const MetaAnalysesPage: React.FC = (props) => { )} - {(metaAnalysis?.user === user?.sub - ? 'Me' - : metaAnalysis?.user) || 'Neurosynth-Compose'} + {/* TODO: fix the model to add the username property */} + {(metaAnalysis as any)?.username || 'Neurosynth-Compose'} ))} diff --git a/compose/neurosynth-frontend/src/pages/Studies/BaseStudyPage/BaseStudyPage.tsx b/compose/neurosynth-frontend/src/pages/Studies/BaseStudyPage/BaseStudyPage.tsx new file mode 100644 index 000000000..b538d2307 --- /dev/null +++ b/compose/neurosynth-frontend/src/pages/Studies/BaseStudyPage/BaseStudyPage.tsx @@ -0,0 +1,101 @@ +import { Box, FormControl, InputLabel, MenuItem, Select } from '@mui/material'; +import DisplayStudy from 'components/DisplayStudy/DisplayStudy'; +import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; +import { useGetStudyById } from 'hooks'; +import useGetBaseStudyById from 'hooks/studies/useGetBaseStudyById'; +import { AnalysisReturn, StudyReturn } from 'neurostore-typescript-sdk'; +import React, { useEffect } from 'react'; +import { useHistory, useParams } from 'react-router-dom'; +import { useInitStudyStore } from '../StudyStore'; +import { studyAnalysesToStoreAnalyses } from '../StudyStore.helpers'; + +const BaseStudyPage: React.FC = (props) => { + const history = useHistory(); + const initStudyStore = useInitStudyStore(); + + const { baseStudyId, studyVersionId }: { baseStudyId: string; studyVersionId?: string } = + useParams<{ + baseStudyId: string; + studyVersionId?: string; + }>(); + const { + data: baseStudy, + isLoading: baseStudyIsLoading, + isError: baseStudyIsError, + } = useGetBaseStudyById(baseStudyId); + + // if studyVersionId doesnt exist, then it will not be queried. + // In the second useEffect hook below, we keep trying to set the studyVersionId + const { + data: study, + isLoading: studyIsLoading, + isError: studyIsError, + } = useGetStudyById(studyVersionId || ''); + + // init the study store with the given version when a new one is set + useEffect(() => { + initStudyStore(studyVersionId); + }, [initStudyStore, studyVersionId]); + + // on initial load, we keep trying to set the URL with the study version until one is set + useEffect(() => { + if (baseStudy && baseStudy.versions && baseStudy.versions.length > 0 && !studyVersionId) { + history.replace( + `/base-studies/${baseStudyId}/${(baseStudy.versions as StudyReturn[])[0].id}` + ); + } + }, [baseStudy, baseStudyId, history, studyVersionId]); + + const analyses = studyAnalysesToStoreAnalyses((study?.analyses || []) as Array); + return ( + + + + Select version to view + + + + + + ); +}; + +export default BaseStudyPage; diff --git a/compose/neurosynth-frontend/src/pages/Studies/StudiesPage/StudiesPage.tsx b/compose/neurosynth-frontend/src/pages/Studies/StudiesPage/StudiesPage.tsx index e4b4fd35f..80f2578f4 100644 --- a/compose/neurosynth-frontend/src/pages/Studies/StudiesPage/StudiesPage.tsx +++ b/compose/neurosynth-frontend/src/pages/Studies/StudiesPage/StudiesPage.tsx @@ -79,7 +79,7 @@ const StudiesPage = () => { // when we search, we want to reset the search criteria as we dont know the // page number of number of results in advance const searchURL = getURLFromSearchCriteria(searchArgs); - history.push(`/studies?${searchURL}`); + history.push(`/base-studies?${searchURL}`); }; const handleRowsPerPageChange = (newRowsPerPage: number) => { @@ -88,12 +88,12 @@ const StudiesPage = () => { 'pageOfResults', '1' ); - history.push(`/studies?${searchURL}`); + history.push(`/base-studies?${searchURL}`); }; const handlePageChange = (page: number) => { const searchURL = addKVPToSearch(location.search, 'pageOfResults', `${page}`); - history.push(`/studies?${searchURL}`); + history.push(`/base-studies?${searchURL}`); }; return ( @@ -156,7 +156,7 @@ const StudiesPage = () => { data-tour={index === 0 ? 'StudiesPage-4' : null} sx={NeurosynthTableStyles.tableRow} key={studyrow.id || index} - onClick={() => history.push(`/studies/${studyrow.id}`)} + onClick={() => history.push(`/base-studies/${studyrow.id}`)} > {studyrow?.name || ( @@ -173,10 +173,7 @@ const StudiesPage = () => { No Publication )} - - {(studyrow?.user === user?.sub ? 'Me' : studyrow?.user) || - 'Neurosynth-Compose'} - + {studyrow?.username || 'Neurosynth-Compose'} ))} /> diff --git a/compose/neurosynth-frontend/src/pages/Studies/StudiesPage/models.ts b/compose/neurosynth-frontend/src/pages/Studies/StudiesPage/models.ts index 4fc334879..7bae4ccaf 100644 --- a/compose/neurosynth-frontend/src/pages/Studies/StudiesPage/models.ts +++ b/compose/neurosynth-frontend/src/pages/Studies/StudiesPage/models.ts @@ -45,17 +45,17 @@ export class SearchCriteria { public pageOfResults: number = 1, public descOrder: boolean = true, public pageSize: number = 10, - public isNested: boolean = false, + public isNested: boolean | undefined = undefined, public nameSearch: string | undefined = undefined, public descriptionSearch: string | undefined = undefined, public authorSearch: string | undefined = undefined, - public showUnique: boolean = true, + public showUnique: boolean | undefined = undefined, public source: Source | undefined = undefined, public publicationSearch: string | undefined = undefined, public userId: string | undefined = undefined, - public dataType: SearchDataType = SearchDataType.ALL, + public dataType: SearchDataType | undefined = SearchDataType.ALL, public studysetOwner: string | undefined = undefined, - public level: 'group' | 'meta' | undefined = 'group', + public level: 'group' | 'meta' | undefined = undefined, public pmid: string | undefined = undefined, public doi: string | undefined = undefined, public flat: boolean | undefined = true, diff --git a/compose/neurosynth-frontend/src/pages/Studies/StudyPage/StudyPage.tsx b/compose/neurosynth-frontend/src/pages/Studies/StudyPage/StudyPage.tsx index 75955af72..e95e0b4ff 100644 --- a/compose/neurosynth-frontend/src/pages/Studies/StudyPage/StudyPage.tsx +++ b/compose/neurosynth-frontend/src/pages/Studies/StudyPage/StudyPage.tsx @@ -1,95 +1,57 @@ -import { Box, FormControl, InputLabel, MenuItem, Select } from '@mui/material'; import DisplayStudy from 'components/DisplayStudy/DisplayStudy'; import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; import { useGetStudyById } from 'hooks'; -import useGetBaseStudyById from 'hooks/studies/useGetBaseStudyById'; -import { AnalysisReturn, StudyReturn } from 'neurostore-typescript-sdk'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; -import { useInitStudyStore } from '../StudyStore'; -import { studyAnalysesToStoreAnalyses } from '../StudyStore.helpers'; +import { + useInitStudyStore, + useStudyAnalyses, + useStudyAuthors, + useStudyDOI, + useStudyDescription, + useStudyIsLoading, + useStudyName, + useStudyPMID, + useStudyPublication, +} from '../StudyStore'; const StudyPage: React.FC = (props) => { - const [selectedVersion, setSelectedVersion] = useState(); // TODO: replace this type with one more precise + const { studyId }: { studyId: string } = useParams<{ + studyId: string; + }>(); const initStudyStore = useInitStudyStore(); - - const { studyId } = useParams<{ studyId: string }>(); - const { - data: baseStudy, - isLoading: baseStudyIsLoading, - isError: baseStudyIsError, - } = useGetBaseStudyById(studyId); - const { - data: study, - isLoading: studyIsLoading, - isError: studyIsError, - } = useGetStudyById(selectedVersion?.id); - - // init the study store with the new version when the selected version changes - useEffect(() => { - initStudyStore(selectedVersion?.id); - }, [initStudyStore, selectedVersion?.id]); - - // on initial load (i.e. if the selectedVersion is not set) then default to the first item + const studyStoreIsLoading = useStudyIsLoading(); + const studyAnalyses = useStudyAnalyses(); + const studyName = useStudyName(); + const studyDescription = useStudyDescription(); + const studyDOI = useStudyDOI(); + const studyAuthors = useStudyAuthors(); + const studyPublication = useStudyPublication(); + const studyPMID = useStudyPMID(); + + // just used for loading + const { isLoading: studyIsLoading, isError: studyIsError } = useGetStudyById(studyId || ''); + + // init the study store with the url is given useEffect(() => { - if (!selectedVersion && baseStudy && baseStudy.versions && baseStudy.versions.length > 0) { - // note: the versions type is a subset of StudyReturn. There are properties in StudyReturn that - // are not part of the versions type - setSelectedVersion((baseStudy.versions as StudyReturn[])[0]); - } - }, [selectedVersion, baseStudy?.versions, baseStudy]); - - const analyses = studyAnalysesToStoreAnalyses((study?.analyses || []) as Array); + initStudyStore(studyId); + }, [initStudyStore, studyId]); return ( - - - Select version to view - - - ); diff --git a/compose/neurosynth-frontend/src/pages/Studysets/StudysetPage/StudysetPage.tsx b/compose/neurosynth-frontend/src/pages/Studysets/StudysetPage/StudysetPage.tsx index 960ca86ac..7ed970a54 100644 --- a/compose/neurosynth-frontend/src/pages/Studysets/StudysetPage/StudysetPage.tsx +++ b/compose/neurosynth-frontend/src/pages/Studysets/StudysetPage/StudysetPage.tsx @@ -1,8 +1,7 @@ import { useAuth0 } from '@auth0/auth0-react'; import AddIcon from '@mui/icons-material/Add'; import HelpIcon from '@mui/icons-material/Help'; -import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; -import { Box, Button, IconButton, Link, TableCell, TableRow, Typography } from '@mui/material'; +import { Box, Button, IconButton, TableCell, TableRow, Typography } from '@mui/material'; import ConfirmationDialog from 'components/Dialogs/ConfirmationDialog/ConfirmationDialog'; import CreateDetailsDialog from 'components/Dialogs/CreateDetailsDialog/CreateDetailsDialog'; import StateHandlerComponent from 'components/StateHandlerComponent/StateHandlerComponent'; @@ -21,7 +20,6 @@ import { StudyReturn } from 'neurostore-typescript-sdk'; import { useState } from 'react'; import { useIsFetching } from 'react-query'; import { useHistory, useParams } from 'react-router'; -import { NavLink } from 'react-router-dom'; import StudysetPageStyles from './StudysetPage.styles'; const StudysetsPage: React.FC = (props) => { @@ -51,7 +49,7 @@ const StudysetsPage: React.FC = (props) => { data: studyset, isLoading: getStudysetIsLoading, isError: getStudysetIsError, - } = useGetStudysetById(params.studysetId); + } = useGetStudysetById(params.studysetId, true); const isFetching = useIsFetching(['studysets', params.studysetId]); const { data: annotations, isLoading: getAnnotationsIsLoading } = useGetAnnotationsByStudysetId( params?.studysetId @@ -297,8 +295,7 @@ const StudysetsPage: React.FC = (props) => { )} - {(annotation?.user === user?.sub ? 'Me' : annotation?.user) || - 'Neurosynth-Compose'} + {annotation?.username || 'Neurosynth-Compose'} ))} @@ -319,10 +316,7 @@ const StudysetsPage: React.FC = (props) => { loaderColor: 'secondary', noDataDisplay: ( - There are no studies in this studyset yet. Start by{' '} - - adding studies to this studyset - + There are no studies in this studyset yet. ), }} @@ -342,16 +336,6 @@ const StudysetsPage: React.FC = (props) => { key: 'journal', styles: { color: 'primary.contrastText', fontWeight: 'bold' }, }, - { - text: '', - key: 'deleteStudyFromStudyset', - styles: { - display: - isAuthenticated && thisUserOwnsthisStudyset - ? 'table-cell' - : 'none', - }, - }, ]} rows={((studyset?.studies || []) as StudyReturn[]).map((study, index) => ( { No Journal )} - - { - event.stopPropagation(); - setDeleteStudyFromStudysetConfirmationIsOpen({ - isOpen: true, - data: { studyId: study.id }, - }); - }} - > - - - ))} /> diff --git a/compose/neurosynth-frontend/src/pages/Studysets/StudysetsPage/StudysetsPage.tsx b/compose/neurosynth-frontend/src/pages/Studysets/StudysetsPage/StudysetsPage.tsx index 01510f1c5..f39b5dcfc 100644 --- a/compose/neurosynth-frontend/src/pages/Studysets/StudysetsPage/StudysetsPage.tsx +++ b/compose/neurosynth-frontend/src/pages/Studysets/StudysetsPage/StudysetsPage.tsx @@ -6,7 +6,7 @@ import NeurosynthTable from 'components/Tables/NeurosynthTable/NeurosynthTable'; import NeurosynthTableStyles from 'components/Tables/NeurosynthTable/NeurosynthTable.styles'; import { useGetStudysets } from 'hooks'; import { StudysetList } from 'neurostore-typescript-sdk'; -import { SearchCriteria } from 'pages/Studies/StudiesPage/models'; +import { SearchCriteria, SortBy } from 'pages/Studies/StudiesPage/models'; import { addKVPToSearch, getNumStudiesString, @@ -17,7 +17,7 @@ import React, { useEffect, useState } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; const StudysetsPage: React.FC = (props) => { - const { user, isLoading: authenticationIsLoading } = useAuth0(); + const { isLoading: authenticationIsLoading } = useAuth0(); const history = useHistory(); const location = useLocation(); @@ -85,7 +85,13 @@ const StudysetsPage: React.FC = (props) => { }; const handleSearch = (search: Partial) => { - const searchURL = getURLFromSearchCriteria(search); + const searchURL = getURLFromSearchCriteria({ + genericSearchStr: search.genericSearchStr || undefined, + sortBy: SortBy.CREATEDAT, + pageOfResults: search.pageOfResults || undefined, + pageSize: search.pageSize || undefined, + descOrder: search.descOrder || undefined, + }); history.push(`/studysets?${searchURL}`); }; @@ -97,6 +103,7 @@ const StudysetsPage: React.FC = (props) => { { No description )} - - {(studyset?.user === user?.sub ? 'Me' : studyset?.user) || - 'Neurosynth-Compose'} - + {studyset?.username || 'Neurosynth-Compose'} ))} /> diff --git a/compose/neurosynth-frontend/src/pages/UserProfilePage/UserProfilePage.tsx b/compose/neurosynth-frontend/src/pages/UserProfilePage/UserProfilePage.tsx new file mode 100644 index 000000000..f1813319b --- /dev/null +++ b/compose/neurosynth-frontend/src/pages/UserProfilePage/UserProfilePage.tsx @@ -0,0 +1,59 @@ +import { LocalStorageCache, useAuth0 } from '@auth0/auth0-react'; +import { Typography } from '@mui/material'; +import { Box } from '@mui/system'; +import CodeSnippet from 'components/CodeSnippet/CodeSnippet'; +import NeurosynthAccordion from 'components/NeurosynthAccordion/NeurosynthAccordion'; +import { useEffect, useState } from 'react'; + +const UserProfilePage: React.FC = (props) => { + const { user, getAccessTokenSilently } = useAuth0(); + const [refreshToken, setRefreshToken] = useState(''); + + useEffect(() => { + const localStorageCache = new LocalStorageCache(); + const keys = localStorageCache.allKeys(); + if (keys.length === 0) { + setRefreshToken(''); + } else { + const auth0Res = localStorageCache.get<{ body?: { refresh_token?: string } }>(keys[0]); + setRefreshToken(auth0Res?.body?.refresh_token || ''); + } + }, [getAccessTokenSilently]); + + return ( + + + User Profile + + + + User: + + + {user?.name} + +
+ + + Email: + + + {user?.email} + +
+
+ + {refreshToken && ( + Refresh Token} + expandIconColor="black" + > + + + )} +
+
+ ); +}; + +export default UserProfilePage; diff --git a/compose/neurosynth_compose/openapi b/compose/neurosynth_compose/openapi index 85e9c2cbb..4be989e69 160000 --- a/compose/neurosynth_compose/openapi +++ b/compose/neurosynth_compose/openapi @@ -1 +1 @@ -Subproject commit 85e9c2cbb4ef306183306c5b651b964e35ca4171 +Subproject commit 4be989e697d603a9c3534de561c786b565c6aa80 diff --git a/store/neurostore/openapi b/store/neurostore/openapi index 198069f10..4be989e69 160000 --- a/store/neurostore/openapi +++ b/store/neurostore/openapi @@ -1 +1 @@ -Subproject commit 198069f108eaf86ec5ff48cb27e7e944a078ca47 +Subproject commit 4be989e697d603a9c3534de561c786b565c6aa80