From 1dbb30449d3ce33e9e60501f5007d969fd23dd4f Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Fri, 15 Nov 2024 16:46:25 -0600 Subject: [PATCH 01/23] Fixed project system naming, improved project/pub state handling --- .../DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx | 6 +++--- .../DataFilesModals/DataFilesShowPathModal.jsx | 6 +++++- .../DataFilesPublicationsList.jsx | 1 + client/src/hooks/datafiles/useSystems.js | 10 +++++++++- client/src/redux/reducers/datafiles.reducers.js | 2 +- client/src/redux/reducers/projects.reducers.js | 5 +++++ client/src/redux/sagas/projects.sagas.js | 11 +++++++++++ client/src/redux/sagas/publications.sagas.js | 9 +++++++++ client/src/utils/systems.js | 4 ++-- 9 files changed, 46 insertions(+), 8 deletions(-) diff --git a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx index 636a4e5f7..829339069 100644 --- a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx +++ b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx @@ -141,7 +141,7 @@ const DataFilesBreadcrumbs = ({ }); }; - const { fetchSelectedSystem } = useSystems(); + const { fetchSelectedSystem, isRootProjectSystem } = useSystems(); const selectedSystem = fetchSelectedSystem({ scheme, system, path }); @@ -184,7 +184,7 @@ const DataFilesBreadcrumbs = ({
{currentDirectory.length === 0 ? ( - {truncateMiddle(systemName || 'Shared Workspaces', 30)} + {truncateMiddle(systemName, 30)} ) : ( currentDirectory.map((pathComp, i) => { @@ -194,7 +194,7 @@ const DataFilesBreadcrumbs = ({ }) )}
- {systemName && api === 'tapis' && ( + {systemName && api === 'tapis' && !isRootProjectSystem(selectedSystem ?? '') && ( diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx index 0b49d5840..79cf2447e 100644 --- a/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx @@ -3,6 +3,7 @@ import { useSelector, useDispatch, shallowEqual } from 'react-redux'; import { Modal, ModalHeader, ModalBody } from 'reactstrap'; import { TextCopyField } from '_common'; import styles from './DataFilesShowPathModal.module.scss'; +import { useSystems } from 'hooks/datafiles'; const DataFilesShowPathModal = React.memo(() => { const dispatch = useDispatch(); @@ -27,8 +28,11 @@ const DataFilesShowPathModal = React.memo(() => { } }, [modalParams, dispatch]); + const { isRootProjectSystem } = useSystems(); + useEffect(() => { - if (params.api === 'tapis' && params.system) { + + if (params.api === 'tapis' && params.system && !isRootProjectSystem({ system: params.system })) { dispatch({ type: 'FETCH_SYSTEM_DEFINITION', payload: params.system, diff --git a/client/src/components/DataFiles/DataFilesPublicationsList/DataFilesPublicationsList.jsx b/client/src/components/DataFiles/DataFilesPublicationsList/DataFilesPublicationsList.jsx index 4bd69ecf4..e17c81016 100644 --- a/client/src/components/DataFiles/DataFilesPublicationsList/DataFilesPublicationsList.jsx +++ b/client/src/components/DataFiles/DataFilesPublicationsList/DataFilesPublicationsList.jsx @@ -38,6 +38,7 @@ const DataFilesPublicationsList = ({ rootSystem }) => { type: 'PUBLICATIONS_GET_PUBLICATIONS', payload: { queryString: query.query_string, + system: selectedSystem.system, }, }); }, [dispatch, query.query_string]); diff --git a/client/src/hooks/datafiles/useSystems.js b/client/src/hooks/datafiles/useSystems.js index c5fa5a65f..884c11674 100644 --- a/client/src/hooks/datafiles/useSystems.js +++ b/client/src/hooks/datafiles/useSystems.js @@ -29,7 +29,15 @@ function useSystems() { [data] ); - return { data, loading, error, fetchSystems, fetchSelectedSystem }; + const isRootProjectSystem = useCallback( + ({ system = '' }) => { + return data.some((s) => + s.scheme === 'projects' && s.system === system + ) + } + ) + + return { data, loading, error, fetchSystems, fetchSelectedSystem, isRootProjectSystem }; } export default useSystems; diff --git a/client/src/redux/reducers/datafiles.reducers.js b/client/src/redux/reducers/datafiles.reducers.js index f3a4d135f..1482d82d4 100644 --- a/client/src/redux/reducers/datafiles.reducers.js +++ b/client/src/redux/reducers/datafiles.reducers.js @@ -418,7 +418,7 @@ export function files(state = initialFilesState, action) { FilesListing: { api: 'tapis', scheme: 'projects', - system: '', + system: action.payload.system ?? '', path: '', }, }, diff --git a/client/src/redux/reducers/projects.reducers.js b/client/src/redux/reducers/projects.reducers.js index 5cdb15c08..5dd4acf2f 100644 --- a/client/src/redux/reducers/projects.reducers.js +++ b/client/src/redux/reducers/projects.reducers.js @@ -221,6 +221,11 @@ export default function projects(state = initialState, action) { ...state, operation: initialState.operation, }; + case 'PROJECTS_CLEAR_METADATA': + return { + ...state, + metadata: initialState.metadata, + }; case 'PROJECTS_CREATE_PUBLICATION_REQUEST_STARTED': return { ...state, diff --git a/client/src/redux/sagas/projects.sagas.js b/client/src/redux/sagas/projects.sagas.js index 12cd8d1bd..e5fc236b5 100644 --- a/client/src/redux/sagas/projects.sagas.js +++ b/client/src/redux/sagas/projects.sagas.js @@ -43,7 +43,15 @@ export function* showSharedWorkspaces(action) { // Clear FileListing params to reset breadcrumbs yield put({ type: 'DATA_FILES_CLEAR_PROJECT_SELECTION', + payload: { + system: action.payload.rootSystem, + } }); + + yield put({ + type: 'PROJECTS_CLEAR_METADATA', + }) + // Load projects list yield put({ type: 'PROJECTS_GET_LISTING', @@ -93,6 +101,9 @@ export async function fetchMetadata(system) { } export function* getMetadata(action) { + yield put({ + type: 'PROJECTS_CLEAR_METADATA', + }) yield put({ type: 'PROJECTS_GET_METADATA_STARTED', }); diff --git a/client/src/redux/sagas/publications.sagas.js b/client/src/redux/sagas/publications.sagas.js index e4f32db23..6d1bffe05 100644 --- a/client/src/redux/sagas/publications.sagas.js +++ b/client/src/redux/sagas/publications.sagas.js @@ -134,6 +134,15 @@ export async function fetchPublicationsUtil(queryString) { } export function* getPublications(action) { + yield put({ + type: 'DATA_FILES_CLEAR_PROJECT_SELECTION', + payload: { + system: action.payload.system, + } + }); + yield put({ + type: 'PROJECTS_CLEAR_METADATA', + }) yield put({ type: 'PUBLICATIONS_GET_PUBLICATIONS_STARTED', }); diff --git a/client/src/utils/systems.js b/client/src/utils/systems.js index fe105a410..cef773e73 100644 --- a/client/src/utils/systems.js +++ b/client/src/utils/systems.js @@ -90,9 +90,9 @@ export function findSystemOrProjectDisplayName( switch (scheme) { case 'projects': let project = findProjectTitle(projectsList, system, projectTitle); - if (!project) { + if (!project) { const projectSystem = systemList.find( - (system) => system.scheme === 'projects' + (sys) => sys.scheme === 'projects' && sys.system === system ); return projectSystem ? projectSystem.name : ''; } else { From 0e5319a3761faa4a47d3000f3e895cbc5deafbf4 Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Tue, 19 Nov 2024 13:24:35 -0600 Subject: [PATCH 02/23] fix for projects breadcrumbs Go To button --- .../DataFilesDropdown/DataFilesDropdown.jsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx index 60e90fce2..981aa944c 100644 --- a/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx +++ b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx @@ -26,7 +26,12 @@ const BreadcrumbsDropdown = ({ const location = useLocation(); const pathParts = location.pathname.split('/'); + const projectId = pathParts.includes('projects') + ? pathParts[pathParts.indexOf('projects') + 2] + : null; + + const rootProjectSystem = pathParts.includes('projects') ? pathParts[pathParts.indexOf('projects') + 1] : null; @@ -35,13 +40,15 @@ const BreadcrumbsDropdown = ({ let url; if (scheme === 'projects' && targetPath === systemName) { - url = `${basePath}/${api}/projects/${projectId}/`; + url = `${basePath}/${api}/projects/${rootProjectSystem}/${projectId}/`; } else if (scheme === 'projects' && !targetPath) { - url = `${basePath}/${api}/projects/`; + url = `${basePath}/${api}/projects/${rootProjectSystem}`; } else if (api === 'googledrive' && !targetPath) { url = `${basePath}/${api}/${scheme}/${system}/`; } else if (api === 'tapis' && scheme !== 'projects' && !targetPath) { url = `${basePath}/${api}/${scheme}/${system}/`; + } else if (scheme === 'projects') { + url = `${basePath}/${api}/projects/${rootProjectSystem}/${system}${targetPath}`; } else { url = `${basePath}/${api}/${scheme}/${system}${targetPath}/`; } @@ -69,7 +76,7 @@ const BreadcrumbsDropdown = ({ ); const sharedWorkspacesDisplayName = systems.find( - (e) => e.scheme === 'projects' + (e) => e.scheme === 'projects' && e.system === rootProjectSystem )?.name; let currentPath = startingPath; @@ -81,7 +88,7 @@ const BreadcrumbsDropdown = ({ const fullPath = paths.reverse(); const displayPaths = scheme === 'projects' ? [...fullPath, systemName] : fullPath; - const sliceStart = scheme === 'projects' && systemName ? 0 : 1; + const sliceStart = 1 return (
Date: Tue, 19 Nov 2024 17:26:09 -0600 Subject: [PATCH 03/23] Added View Authors modal, added extra logic for View Team button --- .../DataFilesModals/DataFilesModals.jsx | 2 + .../DataFilesPublicationAuthorsModal.jsx | 61 +++++++++++++++++++ ...taFilesPublicationAuthorsModal.module.scss | 13 ++++ .../DataFilesProjectFileListing.jsx | 22 +++---- .../DataFilesProjectFileListingAddon.jsx | 14 ++++- .../drp/utils/hooks/useDrpDatasetModals.js | 14 +++++ client/src/hooks/datafiles/useSystems.js | 16 ++++- 7 files changed, 126 insertions(+), 16 deletions(-) create mode 100644 client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.jsx create mode 100644 client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesModals.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesModals.jsx index e250ac0c8..1e8a081d7 100644 --- a/client/src/components/DataFiles/DataFilesModals/DataFilesModals.jsx +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesModals.jsx @@ -23,6 +23,7 @@ import DataFilesProjectTreeModal from './DataFilesProjectTreeModal'; import DataFilesProjectDescriptionModal from './DataFilesProjectDescriptionModal'; import DataFilesViewDataModal from './DataFilesViewDataModal'; import DataFilesProjectCitationModal from './DataFilesProjectCitationModal'; +import DataFilesPublicationAuthorsModal from './DataFilesPublicationAuthorsModal'; export default function DataFilesModals() { return ( @@ -50,6 +51,7 @@ export default function DataFilesModals() { + ); } diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.jsx new file mode 100644 index 000000000..96e8969b3 --- /dev/null +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.jsx @@ -0,0 +1,61 @@ +import InfiniteScrollTable from '_common/InfiniteScrollTable'; +import React, { useCallback, useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { Modal, ModalHeader, ModalBody } from 'reactstrap'; +import styles from './DataFilesPublicationAuthorsModal.module.scss' + + +const DataFilesPublicationAuthorsModal = () => { + + const dispatch = useDispatch(); + const isOpen = useSelector((state) => state.files.modals.publicationAuthors); + const props = useSelector((state) => state.files.modalProps.publicationAuthors); + + const columns = [ + { + Header: 'Name', + accessor: (el) => el, + Cell: (el) => { + const { first_name, last_name } = el.value; + return ( + + {first_name} {last_name} + + ); + }, + }, + { + Header: 'Email', + accessor: 'email', + }, + ]; + + const toggle = useCallback(() => { + dispatch({ + type: 'DATA_FILES_TOGGLE_MODAL', + payload: { operation: 'publicationAuthors', props: {} }, + }); + }, [dispatch]); + + return ( + + + Authors + + + + + + ); +}; + +export default DataFilesPublicationAuthorsModal; \ No newline at end of file diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss new file mode 100644 index 000000000..2a1979deb --- /dev/null +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss @@ -0,0 +1,13 @@ +.author-listing { + /* title */ + th:nth-child(1), + td:nth-child(1) { + width: 50%; + } + /* date */ + th:nth-child(2), + td:nth-child(2) { + width: 50%; + } + } + \ No newline at end of file diff --git a/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx b/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx index 25467417d..cebf71cd7 100644 --- a/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx +++ b/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx @@ -8,18 +8,14 @@ import { SectionMessage, SectionTableWrapper, } from '_common'; -import { useAddonComponents, useFileListing } from 'hooks/datafiles'; +import { useAddonComponents, useFileListing, useSystems } from 'hooks/datafiles'; import DataFilesListing from '../DataFilesListing/DataFilesListing'; import styles from './DataFilesProjectFileListing.module.scss'; const DataFilesProjectFileListing = ({ rootSystem, system, path }) => { const dispatch = useDispatch(); const { fetchListing } = useFileListing('FilesListing'); - const systems = useSelector( - (state) => state.systems.storage.configuration.filter((s) => !s.hidden), - shallowEqual - ); - const [isPublicationSystem, setIsPublicationSystem] = useState(false); + const { isPublicationSystem, isReviewSystem } = useSystems(); // logic to render addonComponents for DRP const portalName = useSelector((state) => state.workbench.portalName); @@ -44,10 +40,6 @@ const DataFilesProjectFileListing = ({ rootSystem, system, path }) => { fetchListing({ api: 'tapis', scheme: 'projects', system, path }); }, [system, path, fetchListing]); - useEffect(() => { - const system = systems.find((s) => s.system === rootSystem); - setIsPublicationSystem(system?.publicationProject); - }, [systems, rootSystem]); const metadata = useSelector((state) => state.projects.metadata); const folderMetadata = useSelector( @@ -135,9 +127,11 @@ const DataFilesProjectFileListing = ({ rootSystem, system, path }) => { | ) : null} - + {!isPublicationSystem(rootSystem) && !isReviewSystem(rootSystem) && ( + + )} {DataFilesProjectFileListingAddon && ( { folderMetadata={folderMetadata} metadata={metadata} path={path} - showCitation={isPublicationSystem} + showCitation={isPublicationSystem(rootSystem)} /> ) : ( diff --git a/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx b/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx index 328f14df8..a44232dfb 100644 --- a/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { Button } from '_common'; import { useDispatch, useSelector } from 'react-redux'; import styles from './DataFilesProjectFileListingAddon.module.scss'; -import { useSelectedFiles } from 'hooks/datafiles'; +import { useSelectedFiles, useSystems } from 'hooks/datafiles'; import useDrpDatasetModals from '../utils/hooks/useDrpDatasetModals'; import { Link } from 'react-router-dom'; import * as ROUTES from '../../../../constants/routes'; @@ -12,6 +12,7 @@ const DataFilesProjectFileListingAddon = ({ rootSystem, system }) => { const { projectId } = useSelector((state) => state.projects.metadata); const { metadata } = useSelector((state) => state.projects); const { selectedFiles } = useSelectedFiles(); + const { isPublicationSystem, isReviewSystem } = useSystems(); const dispatch = useDispatch(); @@ -20,6 +21,7 @@ const DataFilesProjectFileListingAddon = ({ rootSystem, system }) => { createOriginDataModal, createAnalysisDataModal, createTreeModal, + createPublicationAuthorsModal, } = useDrpDatasetModals(projectId, portalName); const createPublicationRequestModal = () => { @@ -79,6 +81,16 @@ const DataFilesProjectFileListingAddon = ({ rootSystem, system }) => { return ( <> + {(isPublicationSystem(rootSystem) || isReviewSystem(rootSystem)) && ( + <> + + + )} {canEditDataset && ( <> | diff --git a/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js b/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js index 6ce6f46ae..010e80ce1 100644 --- a/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js +++ b/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js @@ -142,11 +142,25 @@ const useDrpDatasetModals = ( [dispatch] ); + const createPublicationAuthorsModal = useCallback( + async ({ authors }) => { + dispatch({ + type: 'DATA_FILES_TOGGLE_MODAL', + payload: { + operation: 'publicationAuthors', + props: { authors }, + }, + }); + }, + [dispatch] + ) + return { createSampleModal, createOriginDataModal, createAnalysisDataModal, createTreeModal, + createPublicationAuthorsModal }; }; diff --git a/client/src/hooks/datafiles/useSystems.js b/client/src/hooks/datafiles/useSystems.js index c5fa5a65f..2bad304fd 100644 --- a/client/src/hooks/datafiles/useSystems.js +++ b/client/src/hooks/datafiles/useSystems.js @@ -29,7 +29,21 @@ function useSystems() { [data] ); - return { data, loading, error, fetchSystems, fetchSelectedSystem }; + const isPublicationSystem = useCallback( + (system) => { + return data.some((s) => s.system === system && s.publicationProject); + }, + [data] + ); + + const isReviewSystem = useCallback( + (system) => { + return data.some((s) => s.system === system && s.reviewProject); + }, + [data] + ); + + return { data, loading, error, fetchSystems, fetchSelectedSystem, isPublicationSystem, isReviewSystem }; } export default useSystems; From 9c5543cb7f1665ade725cf428722c7159dfb5e93 Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Wed, 20 Nov 2024 10:54:06 -0600 Subject: [PATCH 04/23] front end linting --- .../DataFilesPublicationAuthorsModal.jsx | 100 +++++++++--------- ...taFilesPublicationAuthorsModal.module.scss | 21 ++-- .../DataFilesProjectFileListing.jsx | 9 +- .../DataFilesProjectFileListingAddon.jsx | 4 +- ...taFilesProjectFileListingMetadataAddon.jsx | 7 +- .../drp/utils/hooks/useDrpDatasetModals.js | 4 +- client/src/hooks/datafiles/useSystems.js | 10 +- 7 files changed, 84 insertions(+), 71 deletions(-) diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.jsx index 96e8969b3..25666ec2d 100644 --- a/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.jsx +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.jsx @@ -2,60 +2,60 @@ import InfiniteScrollTable from '_common/InfiniteScrollTable'; import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Modal, ModalHeader, ModalBody } from 'reactstrap'; -import styles from './DataFilesPublicationAuthorsModal.module.scss' - +import styles from './DataFilesPublicationAuthorsModal.module.scss'; const DataFilesPublicationAuthorsModal = () => { + const dispatch = useDispatch(); + const isOpen = useSelector((state) => state.files.modals.publicationAuthors); + const props = useSelector( + (state) => state.files.modalProps.publicationAuthors + ); - const dispatch = useDispatch(); - const isOpen = useSelector((state) => state.files.modals.publicationAuthors); - const props = useSelector((state) => state.files.modalProps.publicationAuthors); - - const columns = [ - { - Header: 'Name', - accessor: (el) => el, - Cell: (el) => { - const { first_name, last_name } = el.value; - return ( - - {first_name} {last_name} - - ); - }, - }, - { - Header: 'Email', - accessor: 'email', - }, - ]; + const columns = [ + { + Header: 'Name', + accessor: (el) => el, + Cell: (el) => { + const { first_name, last_name } = el.value; + return ( + + {first_name} {last_name} + + ); + }, + }, + { + Header: 'Email', + accessor: 'email', + }, + ]; - const toggle = useCallback(() => { - dispatch({ - type: 'DATA_FILES_TOGGLE_MODAL', - payload: { operation: 'publicationAuthors', props: {} }, - }); - }, [dispatch]); + const toggle = useCallback(() => { + dispatch({ + type: 'DATA_FILES_TOGGLE_MODAL', + payload: { operation: 'publicationAuthors', props: {} }, + }); + }, [dispatch]); - return ( - - - Authors - - - - - - ); + return ( + + + Authors + + + + + + ); }; -export default DataFilesPublicationAuthorsModal; \ No newline at end of file +export default DataFilesPublicationAuthorsModal; diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss index 2a1979deb..dbd530a02 100644 --- a/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss @@ -1,13 +1,12 @@ .author-listing { - /* title */ - th:nth-child(1), - td:nth-child(1) { - width: 50%; - } - /* date */ - th:nth-child(2), - td:nth-child(2) { - width: 50%; - } + /* title */ + th:nth-child(1), + td:nth-child(1) { + width: 50%; } - \ No newline at end of file + /* date */ + th:nth-child(2), + td:nth-child(2) { + width: 50%; + } +} diff --git a/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx b/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx index cebf71cd7..2828e9151 100644 --- a/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx +++ b/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx @@ -8,7 +8,11 @@ import { SectionMessage, SectionTableWrapper, } from '_common'; -import { useAddonComponents, useFileListing, useSystems } from 'hooks/datafiles'; +import { + useAddonComponents, + useFileListing, + useSystems, +} from 'hooks/datafiles'; import DataFilesListing from '../DataFilesListing/DataFilesListing'; import styles from './DataFilesProjectFileListing.module.scss'; @@ -40,7 +44,6 @@ const DataFilesProjectFileListing = ({ rootSystem, system, path }) => { fetchListing({ api: 'tapis', scheme: 'projects', system, path }); }, [system, path, fetchListing]); - const metadata = useSelector((state) => state.projects.metadata); const folderMetadata = useSelector( (state) => state.files.folderMetadata?.FilesListing @@ -129,7 +132,7 @@ const DataFilesProjectFileListing = ({ rootSystem, system, path }) => { ) : null} {!isPublicationSystem(rootSystem) && !isReviewSystem(rootSystem) && ( )} {DataFilesProjectFileListingAddon && ( diff --git a/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx b/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx index a44232dfb..a2099a34c 100644 --- a/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx @@ -85,7 +85,9 @@ const DataFilesProjectFileListingAddon = ({ rootSystem, system }) => { <> diff --git a/client/src/components/_custom/drp/DataFilesProjectFileListingMetadataAddon/DataFilesProjectFileListingMetadataAddon.jsx b/client/src/components/_custom/drp/DataFilesProjectFileListingMetadataAddon/DataFilesProjectFileListingMetadataAddon.jsx index 687965457..e64b4318d 100644 --- a/client/src/components/_custom/drp/DataFilesProjectFileListingMetadataAddon/DataFilesProjectFileListingMetadataAddon.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectFileListingMetadataAddon/DataFilesProjectFileListingMetadataAddon.jsx @@ -38,9 +38,10 @@ const DataFilesProjectFileListingMetadataAddon = ({ const dateLabel = publication_date ? 'Publication Date' : 'Created'; return { - [dateLabel]: new Date( - publication_date || created - ).toLocaleDateString('en-US', dateOptions), + [dateLabel]: new Date(publication_date || created).toLocaleDateString( + 'en-US', + dateOptions + ), license: license ?? 'None', ...(doi && { doi }), ...(keywords && { keywords }), diff --git a/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js b/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js index 010e80ce1..2ba2ecab0 100644 --- a/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js +++ b/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js @@ -153,14 +153,14 @@ const useDrpDatasetModals = ( }); }, [dispatch] - ) + ); return { createSampleModal, createOriginDataModal, createAnalysisDataModal, createTreeModal, - createPublicationAuthorsModal + createPublicationAuthorsModal, }; }; diff --git a/client/src/hooks/datafiles/useSystems.js b/client/src/hooks/datafiles/useSystems.js index 2bad304fd..ded5f08b0 100644 --- a/client/src/hooks/datafiles/useSystems.js +++ b/client/src/hooks/datafiles/useSystems.js @@ -43,7 +43,15 @@ function useSystems() { [data] ); - return { data, loading, error, fetchSystems, fetchSelectedSystem, isPublicationSystem, isReviewSystem }; + return { + data, + loading, + error, + fetchSystems, + fetchSelectedSystem, + isPublicationSystem, + isReviewSystem, + }; } export default useSystems; From 5f7f8b9f9cf389f41770621931415957f21b9d6e Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Wed, 20 Nov 2024 10:55:16 -0600 Subject: [PATCH 05/23] front end linting --- .../DataFilesBreadcrumbs.jsx | 16 +++++++------- .../DataFilesDropdown/DataFilesDropdown.jsx | 4 ++-- .../DataFilesShowPathModal.jsx | 7 +++++-- ...taFilesProjectFileListingMetadataAddon.jsx | 7 ++++--- client/src/hooks/datafiles/useSystems.js | 21 +++++++++++-------- client/src/redux/sagas/projects.sagas.js | 6 +++--- client/src/redux/sagas/publications.sagas.js | 4 ++-- client/src/utils/systems.js | 2 +- 8 files changed, 37 insertions(+), 30 deletions(-) diff --git a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx index 829339069..010d88065 100644 --- a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx +++ b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx @@ -183,9 +183,7 @@ const DataFilesBreadcrumbs = ({
{currentDirectory.length === 0 ? ( - - {truncateMiddle(systemName, 30)} - + {truncateMiddle(systemName, 30)} ) : ( currentDirectory.map((pathComp, i) => { if (i === fullPath.length - 1) { @@ -194,11 +192,13 @@ const DataFilesBreadcrumbs = ({ }) )}
- {systemName && api === 'tapis' && !isRootProjectSystem(selectedSystem ?? '') && ( - - )} + {systemName && + api === 'tapis' && + !isRootProjectSystem(selectedSystem ?? '') && ( + + )}
); }; diff --git a/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx index 981aa944c..33affeef4 100644 --- a/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx +++ b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx @@ -30,7 +30,7 @@ const BreadcrumbsDropdown = ({ const projectId = pathParts.includes('projects') ? pathParts[pathParts.indexOf('projects') + 2] : null; - + const rootProjectSystem = pathParts.includes('projects') ? pathParts[pathParts.indexOf('projects') + 1] : null; @@ -88,7 +88,7 @@ const BreadcrumbsDropdown = ({ const fullPath = paths.reverse(); const displayPaths = scheme === 'projects' ? [...fullPath, systemName] : fullPath; - const sliceStart = 1 + const sliceStart = 1; return (
{ const { isRootProjectSystem } = useSystems(); useEffect(() => { - - if (params.api === 'tapis' && params.system && !isRootProjectSystem({ system: params.system })) { + if ( + params.api === 'tapis' && + params.system && + !isRootProjectSystem({ system: params.system }) + ) { dispatch({ type: 'FETCH_SYSTEM_DEFINITION', payload: params.system, diff --git a/client/src/components/_custom/drp/DataFilesProjectFileListingMetadataAddon/DataFilesProjectFileListingMetadataAddon.jsx b/client/src/components/_custom/drp/DataFilesProjectFileListingMetadataAddon/DataFilesProjectFileListingMetadataAddon.jsx index 687965457..e64b4318d 100644 --- a/client/src/components/_custom/drp/DataFilesProjectFileListingMetadataAddon/DataFilesProjectFileListingMetadataAddon.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectFileListingMetadataAddon/DataFilesProjectFileListingMetadataAddon.jsx @@ -38,9 +38,10 @@ const DataFilesProjectFileListingMetadataAddon = ({ const dateLabel = publication_date ? 'Publication Date' : 'Created'; return { - [dateLabel]: new Date( - publication_date || created - ).toLocaleDateString('en-US', dateOptions), + [dateLabel]: new Date(publication_date || created).toLocaleDateString( + 'en-US', + dateOptions + ), license: license ?? 'None', ...(doi && { doi }), ...(keywords && { keywords }), diff --git a/client/src/hooks/datafiles/useSystems.js b/client/src/hooks/datafiles/useSystems.js index 884c11674..f540ee835 100644 --- a/client/src/hooks/datafiles/useSystems.js +++ b/client/src/hooks/datafiles/useSystems.js @@ -29,15 +29,18 @@ function useSystems() { [data] ); - const isRootProjectSystem = useCallback( - ({ system = '' }) => { - return data.some((s) => - s.scheme === 'projects' && s.system === system - ) - } - ) - - return { data, loading, error, fetchSystems, fetchSelectedSystem, isRootProjectSystem }; + const isRootProjectSystem = useCallback(({ system = '' }) => { + return data.some((s) => s.scheme === 'projects' && s.system === system); + }); + + return { + data, + loading, + error, + fetchSystems, + fetchSelectedSystem, + isRootProjectSystem, + }; } export default useSystems; diff --git a/client/src/redux/sagas/projects.sagas.js b/client/src/redux/sagas/projects.sagas.js index e5fc236b5..220dea631 100644 --- a/client/src/redux/sagas/projects.sagas.js +++ b/client/src/redux/sagas/projects.sagas.js @@ -45,12 +45,12 @@ export function* showSharedWorkspaces(action) { type: 'DATA_FILES_CLEAR_PROJECT_SELECTION', payload: { system: action.payload.rootSystem, - } + }, }); yield put({ type: 'PROJECTS_CLEAR_METADATA', - }) + }); // Load projects list yield put({ @@ -103,7 +103,7 @@ export async function fetchMetadata(system) { export function* getMetadata(action) { yield put({ type: 'PROJECTS_CLEAR_METADATA', - }) + }); yield put({ type: 'PROJECTS_GET_METADATA_STARTED', }); diff --git a/client/src/redux/sagas/publications.sagas.js b/client/src/redux/sagas/publications.sagas.js index 6d1bffe05..355a4c450 100644 --- a/client/src/redux/sagas/publications.sagas.js +++ b/client/src/redux/sagas/publications.sagas.js @@ -138,11 +138,11 @@ export function* getPublications(action) { type: 'DATA_FILES_CLEAR_PROJECT_SELECTION', payload: { system: action.payload.system, - } + }, }); yield put({ type: 'PROJECTS_CLEAR_METADATA', - }) + }); yield put({ type: 'PUBLICATIONS_GET_PUBLICATIONS_STARTED', }); diff --git a/client/src/utils/systems.js b/client/src/utils/systems.js index cef773e73..594fe8dcf 100644 --- a/client/src/utils/systems.js +++ b/client/src/utils/systems.js @@ -90,7 +90,7 @@ export function findSystemOrProjectDisplayName( switch (scheme) { case 'projects': let project = findProjectTitle(projectsList, system, projectTitle); - if (!project) { + if (!project) { const projectSystem = systemList.find( (sys) => sys.scheme === 'projects' && sys.system === system ); From 6e19162a9070efba89e1b7b524609e144b5decca Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Wed, 20 Nov 2024 11:28:30 -0600 Subject: [PATCH 06/23] Email project authors when their publication request is accepted/rejected (#1008) --- .../project_publish_operations.py | 71 ++++++++++++++++++- server/portal/apps/publications/views.py | 6 +- server/portal/settings/settings.py | 10 +++ .../settings/settings_custom.example.py | 4 ++ 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/server/portal/apps/projects/workspace_operations/project_publish_operations.py b/server/portal/apps/projects/workspace_operations/project_publish_operations.py index bad0c781f..bbcc86031 100644 --- a/server/portal/apps/projects/workspace_operations/project_publish_operations.py +++ b/server/portal/apps/projects/workspace_operations/project_publish_operations.py @@ -14,6 +14,7 @@ from tapipy.errors import NotFoundError, BaseTapyException from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist +from django.core.mail import send_mail logger = logging.getLogger(__name__) @@ -248,4 +249,72 @@ def update_and_cleanup_review_project(review_project_id: str, status: Publicatio review_project_graph.delete() review_project.delete() - logger.info(f'Deleted review project {review_project_id} and its associated data.') \ No newline at end of file + logger.info(f'Deleted review project {review_project_id} and its associated data.') + + +def get_project_user_emails(project_id): + """Return a list of emails for users in a project.""" + prj = ProjectMetadata.get_project_by_id(project_id) + return [user["email"] for user in prj.value["authors"] if user.get("email")] + + +@shared_task(bind=True, queue='default') +def send_publication_accept_email(project_id): + """ + Alert project authors that their request has been accepted. + """ + user_emails = get_project_user_emails() + for user_email in user_emails: + email_body = f""" +

Hello,

+

+ Congratulations! The following project has been accepted for publication: +
+ {project_id} +
+

+

+ Your publication should appear in the portal within 1 business day. +

+ + This is a programmatically generated message. Do NOT reply to this message. + """ + + send_mail( + "DigitalRocks Alert: Your Publication Request has been Accepted", + email_body, + settings.DEFAULT_FROM_EMAIL, + [user_email], + html_message=email_body, + ) + + +@shared_task(bind=True, queue='default') +def send_publication_reject_email(project_id: str, version: Optional[int], error: str): + """ + Alert project authors that their request has been rejected. + """ + user_emails = get_project_user_emails() + for user_email in user_emails: + email_body = f""" +

Hello,

+

+ The following project has been rejected by a reviewer and cannot be published at this time: +
+ {project_id} +
+

+

+ You are welcome to revise this project and re-submit for publication. +

+ + This is a programmatically generated message. Do NOT reply to this message. + """ + + send_mail( + "DigitalRocks Alert: Your Publication Request has been Rejected", + email_body, + settings.DEFAULT_FROM_EMAIL, + [user_email], + html_message=email_body, + ) \ No newline at end of file diff --git a/server/portal/apps/publications/views.py b/server/portal/apps/publications/views.py index 55aeef777..2eab42e1f 100644 --- a/server/portal/apps/publications/views.py +++ b/server/portal/apps/publications/views.py @@ -12,7 +12,7 @@ from portal.exceptions.api import ApiException from portal.views.base import BaseApiView from portal.apps.projects.workspace_operations.shared_workspace_operations import create_publication_workspace -from portal.apps.projects.workspace_operations.project_publish_operations import copy_graph_and_files_for_review_system, publish_project, update_and_cleanup_review_project +from portal.apps.projects.workspace_operations.project_publish_operations import copy_graph_and_files_for_review_system, publish_project, update_and_cleanup_review_project, send_publication_accept_email, send_publication_reject_email from portal.apps.projects.models.metadata import ProjectsMetadata from django.db import transaction from portal.apps.notifications.models import Notification @@ -220,6 +220,8 @@ def post(self, request): if not full_project_id: raise ApiException("Missing project ID", status=400) + send_publication_accept_email.apply_async(args=[full_project_id]) + if is_review: project_id = full_project_id.split(f"{settings.PORTAL_PROJECTS_REVIEW_SYSTEM_PREFIX}.")[1] else: @@ -314,6 +316,8 @@ def post(self, request): if not full_project_id: raise ApiException("Missing project ID", status=400) + send_publication_reject_email.apply_async(args=[full_project_id]) + update_and_cleanup_review_project(full_project_id, PublicationRequest.Status.REJECTED) # Create notification diff --git a/server/portal/settings/settings.py b/server/portal/settings/settings.py index bdd8f8633..8daa52252 100644 --- a/server/portal/settings/settings.py +++ b/server/portal/settings/settings.py @@ -755,6 +755,16 @@ PORTAL_ELEVATED_ROLES = getattr(settings_custom, '_PORTAL_ELEVATED_ROLES', {}) +""" +SETTINGS: EMAIL +""" +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_HOST = getattr(settings_custom, '_SMTP_HOST', 'localhost') +EMAIL_PORT = getattr(settings_custom, '_SMTP_PORT', 25) +EMAIL_HOST_USER = getattr(settings_custom, '_SMTP_USER', '') +EMAIL_HOST_PASSWORD = getattr(settings_custom, '_SMTP_PASSWORD', '') +DEFAULT_FROM_EMAIL = getattr(settings_custom, '_DEFAULT_FROM_EMAIL', '') + """ SETTINGS: INTERNAL DOCS """ diff --git a/server/portal/settings/settings_custom.example.py b/server/portal/settings/settings_custom.example.py index 021148513..dee98e741 100644 --- a/server/portal/settings/settings_custom.example.py +++ b/server/portal/settings/settings_custom.example.py @@ -273,3 +273,7 @@ "usernames": [] } } + + +_SMTP_HOST = "relay.tacc.utexas.edu" +_DEFAULT_FROM_EMAIL="no-reply@digitalrocksportal.org" \ No newline at end of file From 941a2d24e77ecec02c59d1e4ace143429ac6cffa Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Wed, 20 Nov 2024 11:42:41 -0600 Subject: [PATCH 07/23] Changed curator name to email link when requesting publications --- .../SubmitPublicationRequest.jsx | 2 +- .../SubmitPublicationReview.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationRequest.jsx b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationRequest.jsx index 3ccff7617..c110696ab 100644 --- a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationRequest.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationRequest.jsx @@ -85,7 +85,7 @@ const SubmitPublicationRequest = ({ callbackUrl }) => {
If you have any doubts about the process please contact the data - curator Maria Esteva before submitting the data for publication. + curator Maria Esteva before submitting the data for publication.
) : null}
+ {DataFilesManageProjectModalAddon && ( + + )}
diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesProjectEditDescriptionModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesProjectEditDescriptionModal.jsx index 8172e234d..0a07f9f1f 100644 --- a/client/src/components/DataFiles/DataFilesModals/DataFilesProjectEditDescriptionModal.jsx +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesProjectEditDescriptionModal.jsx @@ -60,6 +60,7 @@ const DataFilesProjectEditDescriptionModal = () => { description: values.description || '', metadata: DataFilesProjectEditDescriptionModalAddon ? values : null, }, + modal: 'editproject', }, }); }, diff --git a/client/src/components/_custom/drp/DataFilesManageProjectModalAddon/DataFilesManageProjectModalAddon.jsx b/client/src/components/_custom/drp/DataFilesManageProjectModalAddon/DataFilesManageProjectModalAddon.jsx new file mode 100644 index 000000000..fed1aaaa5 --- /dev/null +++ b/client/src/components/_custom/drp/DataFilesManageProjectModalAddon/DataFilesManageProjectModalAddon.jsx @@ -0,0 +1,251 @@ +import React, { useState, useCallback, useEffect, useMemo } from 'react'; +import * as Yup from 'yup'; +import styles from './DataFilesManageProjectModalAddon.module.scss'; +import { useDispatch, useSelector } from 'react-redux'; +import { useSystemRole } from '../../../DataFiles/DataFilesProjectMembers/_cells/SystemRoleSelector'; +import { Input, Label } from 'reactstrap'; +import { FieldArray, Form, Formik, useFormikContext } from 'formik'; +import { + Button, + FormField, + InfiniteScrollTable, + LoadingSpinner, + Section, +} from '_common'; + +const DataFilesManageProjectModalAddon = ({ projectId }) => { + const dispatch = useDispatch(); + + const { metadata } = useSelector((state) => state.projects); + + const { loading, error } = useSelector((state) => { + if ( + state.projects.operation && + state.projects.operation.name === 'titleDescription' + ) { + return state.projects.operation; + } + return { + loading: false, + error: false, + }; + }); + + const authenticatedUser = useSelector( + (state) => state.authenticatedUser.user.username + ); + + const { query: authenticatedUserQuery } = useSystemRole( + projectId ?? null, + authenticatedUser ?? null + ); + + const canEditSystem = ['OWNER', 'ADMIN'].includes( + authenticatedUserQuery?.data?.role + ); + + const readOnlyTeam = useSelector((state) => { + const projectSystem = state.systems.storage.configuration.find( + (s) => s.scheme === 'projects' + ); + + return projectSystem?.readOnly || !canEditSystem; + }); + + const handleRemoveGuestUser = useCallback( + (email) => { + const updatedGuestUsers = metadata.guest_users.filter( + (user) => user.email !== email + ); + + dispatch({ + type: 'PROJECTS_SET_TITLE_DESCRIPTION', + payload: { + projectId, + data: { + title: metadata.title, + description: metadata.description || '', + metadata: { + guest_users: updatedGuestUsers, + }, + }, + modal: '', + }, + }); + }, + [metadata, dispatch, projectId] // Dependency array + ); + + const columns = [ + { + Header: 'Guest Members', + accessor: (el) => el, + Cell: (el) => { + const { first_name, last_name, email } = el.value; + return ( + + {`${first_name} ${last_name}`} + {` (${email})`} + + ); + }, + }, + { + Header: loading ? ( + + ) : ( + '' + ), + accessor: 'email', + Cell: (el) => { + return !readOnlyTeam ? ( + + ) : null; + }, + }, + ]; + + const onSubmit = useCallback( + (values) => { + dispatch({ + type: 'PROJECTS_SET_TITLE_DESCRIPTION', + payload: { + projectId, + data: { + title: metadata.title, + description: metadata.description || '', + metadata: { + guest_users: [...metadata.guest_users, ...values.guestUsers], + }, + }, + modal: '', + }, + }); + }, + [projectId, dispatch, metadata] + ); + + const validationSchema = Yup.object().shape({ + guestUsers: Yup.array().of( + Yup.object().shape({ + first_name: Yup.string().required('First Name is required'), + last_name: Yup.string().required('Last Name is required'), + email: Yup.string() + .email('Invalid email address') + .required('Email is required'), + }) + ), + }); + + return ( +
+ {metadata?.guest_users?.length > 0 && ( + + )} + + {!readOnlyTeam && ( + <> + + + {({ values, isValid, resetForm }) => { + useEffect(() => { + resetForm(); + }, [metadata, resetForm]); + + return ( +
+ + {({ remove, push }) => ( + <> + {/* Render only if there are guest users */} + {values.guestUsers.length > 0 && + values.guestUsers.map((_, index) => ( +
+ + + + + + + } + /> + ))} + + {/* Button to add a new guest user */} + + + )} + + + ); + }} + + + )} +
+ ); +}; + +export default DataFilesManageProjectModalAddon; diff --git a/client/src/components/_custom/drp/DataFilesManageProjectModalAddon/DataFilesManageProjectModalAddon.module.scss b/client/src/components/_custom/drp/DataFilesManageProjectModalAddon/DataFilesManageProjectModalAddon.module.scss new file mode 100644 index 000000000..17b8c2b5a --- /dev/null +++ b/client/src/components/_custom/drp/DataFilesManageProjectModalAddon/DataFilesManageProjectModalAddon.module.scss @@ -0,0 +1,45 @@ +.form-div { + display: flex; + justify-content: space-between; + width: 100%; + // align-items: center; +} + +.form-div > *:not(:nth-last-child(-n + 2)) { + width: 25%; + margin-bottom: 50px; +} + +.remove-button { + align-self: center; + margin-bottom: 15px; +} + +.button-full { + min-width: 100% !important; + margin-bottom: 30px; +} + +.printed-name { + font-weight: bold; +} + +.guest-user-listing { + /* title */ + th:nth-child(1), + td:nth-child(1) { + width: 80%; + } + /* date */ + th:nth-child(2), + td:nth-child(2) { + width: 20%; + } + + padding-bottom: 10px; +} + +.guest-members__loading { + font-size: 1em; + justify-content: flex-end; +} diff --git a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ReviewAuthors.jsx b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ReviewAuthors.jsx index 49572392c..aef8fe648 100644 --- a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ReviewAuthors.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ReviewAuthors.jsx @@ -1,19 +1,13 @@ import React, { useEffect, useState } from 'react'; import { Button, - ShowMore, - LoadingSpinner, - SectionMessage, SectionTableWrapper, - DescriptionList, Section, - SectionContent, - Expand, } from '_common'; import styles from './DataFilesProjectPublishWizard.module.scss'; import ReorderUserList from '../../utils/ReorderUserList/ReorderUserList'; import ProjectMembersList from '../../utils/ProjectMembersList/ProjectMembersList'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; const ACMCitation = ({ project, authors }) => { const authorString = authors @@ -157,6 +151,8 @@ const ReviewAuthors = ({ project, onAuthorsUpdate }) => { const [authors, setAuthors] = useState([]); const [members, setMembers] = useState([]); + const dispatch = useDispatch(); + const canEdit = useSelector((state) => { const { members } = state.projects.metadata; const { username } = state.authenticatedUser.user; @@ -187,8 +183,10 @@ const ReviewAuthors = ({ project, onAuthorsUpdate }) => { ) .map((user) => user.user); + const guestUsers = project.guest_users || []; + setAuthors(owners); - setMembers(members); + setMembers([...members, ...guestUsers]); onAuthorsUpdate(owners); }, [project]); @@ -211,9 +209,29 @@ const ReviewAuthors = ({ project, onAuthorsUpdate }) => { onAuthorsUpdate(newAuthors); }; + const onManageTeam = () => { + dispatch({ + type: 'DATA_FILES_TOGGLE_MODAL', + payload: { operation: 'manageproject', props: {} }, + }); + }; + return ( Review Authors and Citation
} + header={
Review Authors and Citations
} + headerActions={ + <> + {canEdit && ( +
+ <> + + +
+ )} + + } > {authors.length > 0 && project && (
diff --git a/client/src/redux/reducers/projects.reducers.js b/client/src/redux/reducers/projects.reducers.js index 5dd4acf2f..86e007912 100644 --- a/client/src/redux/reducers/projects.reducers.js +++ b/client/src/redux/reducers/projects.reducers.js @@ -150,7 +150,8 @@ export default function projects(state = initialState, action) { return { ...state, metadata: { - ...action.payload, + ...state.metadata, + members: action.payload.members, loading: false, error: null, }, diff --git a/client/src/redux/sagas/projects.sagas.js b/client/src/redux/sagas/projects.sagas.js index 220dea631..452826253 100644 --- a/client/src/redux/sagas/projects.sagas.js +++ b/client/src/redux/sagas/projects.sagas.js @@ -175,7 +175,7 @@ export function* setTitleDescription(action) { type: 'PROJECTS_SET_TITLE_DESCRIPTION_STARTED', }); try { - const { projectId, data } = action.payload; + const { projectId, data, modal } = action.payload; const metadata = yield call(setTitleDescriptionUtil, projectId, data); yield put({ type: 'PROJECTS_SET_TITLE_DESCRIPTION_SUCCESS', @@ -183,7 +183,7 @@ export function* setTitleDescription(action) { }); yield put({ type: 'DATA_FILES_TOGGLE_MODAL', - payload: { operation: 'editproject', props: {} }, + payload: { operation: modal, props: {} }, }); yield put({ type: 'PROJECTS_GET_LISTING', diff --git a/server/portal/apps/_custom/drp/models.py b/server/portal/apps/_custom/drp/models.py index 99662cc6e..999aa1434 100644 --- a/server/portal/apps/_custom/drp/models.py +++ b/server/portal/apps/_custom/drp/models.py @@ -101,6 +101,17 @@ class DrpProjectRelatedPublications(DrpMetadataModel): publication_description: Optional[str] = None publication_link: Optional[str] = None +class DrpGuestUser(DrpMetadataModel): + """Model for DRP Guest User""" + + model_config = ConfigDict( + extra="forbid", + ) + + first_name: str + last_name: str + email: str + class DrpProjectMetadata(DrpMetadataModel): """Model for DRP Project Metadata""" @@ -122,6 +133,7 @@ class DrpProjectMetadata(DrpMetadataModel): file_objs: list[FileObj] = [] is_review_project: Optional[bool] = None is_published_project: Optional[bool] = None + guest_users: list[DrpGuestUser] = [] class DrpDatasetMetadata(DrpMetadataModel): """Model for Base DRP Dataset Metadata""" diff --git a/server/portal/apps/projects/workspace_operations/project_meta_operations.py b/server/portal/apps/projects/workspace_operations/project_meta_operations.py index 0429b6789..8cc480dbe 100644 --- a/server/portal/apps/projects/workspace_operations/project_meta_operations.py +++ b/server/portal/apps/projects/workspace_operations/project_meta_operations.py @@ -155,11 +155,8 @@ def patch_project_entity(project_id, value): entity = ProjectMetadata.get_project_by_id(project_id) schema_model = SCHEMA_MAPPING[entity.name] - patched_metadata = {**value, - 'projectId': project_id, - 'fileObjs': entity.value.get('fileObjs', []), - 'doi': entity.value.get('doi', None) - } + entity_value = get_ordered_value(entity.name, entity.value) + patched_metadata = {**entity_value, **value} update_node_in_project(project_id, 'NODE_ROOT', None, value.get('title')) From c414ddec822a607b11b0befbb66a135fc94f637f Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Sun, 24 Nov 2024 19:19:29 -0600 Subject: [PATCH 13/23] small bug fix --- .../workspace_operations/shared_workspace_operations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py b/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py index 49ce1c05a..062d48069 100644 --- a/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py +++ b/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py @@ -341,6 +341,9 @@ def list_projects(client, root_system_id=None): is_review_system = root_system.get('reviewProject', False) is_publication_system = root_system.get('publicationProject', False) + else: + is_review_system = False + is_publication_system = False community_system = next(system for system in settings.PORTAL_DATAFILES_STORAGE_SYSTEMS if system['scheme'] == 'community') From e536d7ba0e9865bb2f60962b44616c8a846e4811 Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Sun, 24 Nov 2024 20:10:11 -0600 Subject: [PATCH 14/23] update default settings --- server/portal/settings/settings_default.py | 47 +++++++++++----------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/server/portal/settings/settings_default.py b/server/portal/settings/settings_default.py index eb12d24d9..1c15ed03e 100644 --- a/server/portal/settings/settings_default.py +++ b/server/portal/settings/settings_default.py @@ -84,15 +84,6 @@ 'homeDir': '/home1/{tasdir}', 'icon': None, }, - { - 'name': 'Community Data', - 'system': 'cloud.data', - 'scheme': 'community', - 'api': 'tapis', - 'homeDir': '/corral/tacc/aci/CEP/community', - 'icon': None, - 'siteSearchPriority': 1 - }, { 'name': 'Public Data', 'system': 'cloud.data', @@ -102,6 +93,15 @@ 'icon': 'publications', 'siteSearchPriority': 0 }, + { + "name": "Community Data", + "system": "cloud.data", + "scheme": "community", + "api": "tapis", + "homeDir": "/corral-repl/utexas/OTH21076/data_pprd/community", + "icon": None, + "siteSearchPriority": 0, + }, { 'name': 'Projects', 'scheme': 'projects', @@ -110,18 +110,18 @@ 'readOnly': False, 'hideSearchBar': False, 'defaultProject': True, - 'system': 'cep.project.root', - 'rootDir': '/corral-repl/tacc/aci/CEP/projects', + 'system': 'drp.pprd.project.root', + 'rootDir': '/corral-repl/utexas/OTH21076/data_pprd/projects', }, - { + { 'name': 'Published', 'scheme': 'projects', 'api': 'tapis', 'icon': 'publications', 'readOnly': True, 'hideSearchBar': False, - 'system': 'drp.project.published.test', - 'rootDir': '/corral-repl/utexas/pge-nsf/data_pprd/published', + 'system': 'drp.pprd.project.published', + 'rootDir': '/corral-repl/utexas/OTH21076/data_pprd/published', 'publicationProject': True, }, { @@ -131,8 +131,8 @@ 'icon': 'publications', 'readOnly': True, 'hideSearchBar': False, - 'system': 'drp.project.review.test', - 'rootDir': '/corral-repl/utexas/pge-nsf/data_pprd/test', + 'system': 'drp.pprd.project.review', + 'rootDir': '/corral-repl/utexas/OTH21076/data_pprd/review', 'reviewProject': True, } ] @@ -206,20 +206,20 @@ _PORTAL_PROJECTS_SYSTEM_PREFIX = 'cep.project' _PORTAL_PROJECTS_ID_PREFIX = 'CEPV3-DEV' -_PORTAL_PROJECTS_ROOT_DIR = '/corral-repl/tacc/aci/CEP/projects' -_PORTAL_PROJECTS_ROOT_SYSTEM_NAME = 'cep.project.root' +_PORTAL_PROJECTS_ROOT_DIR = '/corral-repl/utexas/OTH21076/data_pprd/projects' +_PORTAL_PROJECTS_ROOT_SYSTEM_NAME = 'drp.pprd.project.root' _PORTAL_PROJECTS_ROOT_HOST = 'cloud.data.tacc.utexas.edu' _PORTAL_PROJECTS_SYSTEM_PORT = "22" _PORTAL_PROJECTS_PEMS_APP_ID = "" # Defunct in v3 _PORTAL_PROJECTS_USE_SET_FACL_JOB = False _PORTAL_PROJECTS_REVIEW_SYSTEM_PREFIX = 'cep.project.review' -_PORTAL_PROJECTS_REVIEW_ROOT_DIR = '/corral-repl/utexas/pge-nsf/data_pprd/test' -_PORTAL_PROJECTS_ROOT_REVIEW_SYSTEM_NAME = 'drp.project.review.test' +_PORTAL_PROJECTS_REVIEW_ROOT_DIR = '/corral-repl/utexas/OTH21076/data_pprd/review' +_PORTAL_PROJECTS_ROOT_REVIEW_SYSTEM_NAME = 'drp.pprd.project.review' _PORTAL_PROJECTS_PUBLISHED_SYSTEM_PREFIX = 'cep.project.published' -_PORTAL_PROJECTS_PUBLISHED_ROOT_DIR = '/corral-repl/utexas/pge-nsf/data_pprd/published' -_PORTAL_PROJECTS_PUBLISHED_ROOT_SYSTEM_NAME = 'drp.project.published.test' +_PORTAL_PROJECTS_PUBLISHED_ROOT_DIR = '/corral-repl/utexas/OTH21076/data_pprd/published' +_PORTAL_PROJECTS_PUBLISHED_ROOT_SYSTEM_NAME = 'drp.pprd.project.published' _PORTAL_PUBLICATION_REVIEWERS_GROUP_NAME = 'PROJECT_REVIEWER' @@ -272,7 +272,8 @@ "hasCustomDataFilesToolbarChecks": True, "addons": ['DataFilesProjectFileListingAddon', 'DataFilesAddProjectModalAddon', 'DataFilesProjectEditDescriptionModalAddon', 'DataFilesProjectFileListingMetadataAddon', 'DataFilesProjectFileListingMetadataTitleAddon', - 'DataFilesUploadModalAddon', 'DataFilesPreviewModalAddon', 'DataFilesProjectPublish', 'DataFilesProjectReview'], + 'DataFilesUploadModalAddon', 'DataFilesPreviewModalAddon', 'DataFilesProjectPublish', 'DataFilesProjectReview', + 'DataFilesManageProjectModalAddon'], "showDataFileType": True, "onboardingCompleteRedirect": '/workbench/', "noPHISystem": "", From de6d2e1c585ee0295232f593291ec5195be7a633 Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Mon, 25 Nov 2024 10:20:39 -0600 Subject: [PATCH 15/23] project publish bug fix --- .../workspace_operations/project_publish_operations.py | 4 ++-- server/portal/apps/publications/views.py | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/portal/apps/projects/workspace_operations/project_publish_operations.py b/server/portal/apps/projects/workspace_operations/project_publish_operations.py index bbcc86031..1d8a83fff 100644 --- a/server/portal/apps/projects/workspace_operations/project_publish_operations.py +++ b/server/portal/apps/projects/workspace_operations/project_publish_operations.py @@ -259,7 +259,7 @@ def get_project_user_emails(project_id): @shared_task(bind=True, queue='default') -def send_publication_accept_email(project_id): +def send_publication_accept_email(self, project_id): """ Alert project authors that their request has been accepted. """ @@ -290,7 +290,7 @@ def send_publication_accept_email(project_id): @shared_task(bind=True, queue='default') -def send_publication_reject_email(project_id: str, version: Optional[int], error: str): +def send_publication_reject_email(self, project_id: str, version: Optional[int], error: str): """ Alert project authors that their request has been rejected. """ diff --git a/server/portal/apps/publications/views.py b/server/portal/apps/publications/views.py index 2eab42e1f..f4f17397c 100644 --- a/server/portal/apps/publications/views.py +++ b/server/portal/apps/publications/views.py @@ -220,7 +220,8 @@ def post(self, request): if not full_project_id: raise ApiException("Missing project ID", status=400) - send_publication_accept_email.apply_async(args=[full_project_id]) + if not settings.DEBUG: + send_publication_accept_email.apply_async(args=[full_project_id]) if is_review: project_id = full_project_id.split(f"{settings.PORTAL_PROJECTS_REVIEW_SYSTEM_PREFIX}.")[1] @@ -316,7 +317,8 @@ def post(self, request): if not full_project_id: raise ApiException("Missing project ID", status=400) - send_publication_reject_email.apply_async(args=[full_project_id]) + if not settings.DEBUG: + send_publication_reject_email.apply_async(args=[full_project_id]) update_and_cleanup_review_project(full_project_id, PublicationRequest.Status.REJECTED) From 3a92605f720a2687148d6bafc6dc6f639dc7594b Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Mon, 25 Nov 2024 10:53:47 -0600 Subject: [PATCH 16/23] publish send email bug fix, disable publish button --- .../SubmitPublicationReview.jsx | 4 +++- .../workspace_operations/project_publish_operations.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationReview.jsx b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationReview.jsx index ec1cb8aa2..7bde41e0a 100644 --- a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationReview.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationReview.jsx @@ -17,6 +17,8 @@ const SubmitPublicationReview = ({ callbackUrl }) => { const [submitDisabled, setSubmitDisabled] = useState(false); + const { debug } = useSelector((state) => state.workbench.config); + const { isApproveLoading, isRejectLoading, @@ -83,7 +85,7 @@ const SubmitPublicationReview = ({ callbackUrl }) => { | diff --git a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublish.jsx b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublish.jsx index 25dc6d63e..bee5d4bd2 100644 --- a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublish.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublish.jsx @@ -110,7 +110,7 @@ const DataFilesProjectPublish = ({ rootSystem, system }) => { className={styles.root} header={
- Request Project Publication | {metadata.title} + Request Dataset Publication | {metadata.title}
} headerActions={ @@ -120,7 +120,7 @@ const DataFilesProjectPublish = ({ rootSystem, system }) => { className="wb-link" to={`${ROUTES.WORKBENCH}${ROUTES.DATA}/tapis/projects/${rootSystem}/${system}`} > - Back to Project + Back to Dataset diff --git a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ProjectDescription.jsx b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ProjectDescription.jsx index e314d0a3b..99c7a593b 100644 --- a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ProjectDescription.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ProjectDescription.jsx @@ -134,14 +134,14 @@ const ProjectDescription = ({ project }) => { return ( Proofread Project} + header={
Proofread Dataset
} headerActions={ <> {canEdit && (
<>
diff --git a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ReviewProjectStructure.jsx b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ReviewProjectStructure.jsx index 8d8edef5e..9c8c787da 100644 --- a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ReviewProjectStructure.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/ReviewProjectStructure.jsx @@ -46,7 +46,7 @@ export const ReviewProjectStructure = ({ projectTree }) => {
<>
diff --git a/client/src/components/_custom/drp/DataFilesProjectReview/DataFilesProjectReview.jsx b/client/src/components/_custom/drp/DataFilesProjectReview/DataFilesProjectReview.jsx index 6c660720a..f11acc224 100644 --- a/client/src/components/_custom/drp/DataFilesProjectReview/DataFilesProjectReview.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectReview/DataFilesProjectReview.jsx @@ -98,7 +98,7 @@ const DataFilesProjectReview = ({ rootSystem, system }) => { className="wb-link" to={`${ROUTES.WORKBENCH}${ROUTES.DATA}/tapis/projects/${rootSystem}/${system}`} > - Back to Project + Back to Dataset From 271ce8c2b88fdc9d92613a954af42f6489f52e09 Mon Sep 17 00:00:00 2001 From: shayanaijaz Date: Wed, 4 Dec 2024 16:58:42 -0600 Subject: [PATCH 20/23] publication rejection email bug fix --- .../workspace_operations/project_publish_operations.py | 2 +- server/portal/settings/settings_default.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/server/portal/apps/projects/workspace_operations/project_publish_operations.py b/server/portal/apps/projects/workspace_operations/project_publish_operations.py index 0157b41b8..1cd37ac71 100644 --- a/server/portal/apps/projects/workspace_operations/project_publish_operations.py +++ b/server/portal/apps/projects/workspace_operations/project_publish_operations.py @@ -290,7 +290,7 @@ def send_publication_accept_email(self, project_id): @shared_task(bind=True, queue='default') -def send_publication_reject_email(self, project_id: str, version: Optional[int], error: str): +def send_publication_reject_email(self, project_id: str): """ Alert project authors that their request has been rejected. """ diff --git a/server/portal/settings/settings_default.py b/server/portal/settings/settings_default.py index 813ec0405..6ba54927f 100644 --- a/server/portal/settings/settings_default.py +++ b/server/portal/settings/settings_default.py @@ -103,7 +103,7 @@ "siteSearchPriority": 0, }, { - 'name': 'Projects', + 'name': 'Dataset', 'scheme': 'projects', 'api': 'tapis', 'icon': 'publications', @@ -114,7 +114,7 @@ 'rootDir': '/corral-repl/utexas/OTH21076/data_pprd/projects', }, { - 'name': 'Published', + 'name': 'Published Datasets', 'scheme': 'projects', 'api': 'tapis', 'icon': 'publications', From c3dfa0029975d73ba8f6105c911ea2f2ef3a563d Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Tue, 10 Dec 2024 11:44:37 -0600 Subject: [PATCH 21/23] Fix case bug in project metadata search (#1001) Co-authored-by: Shayan Khan --- server/portal/apps/projects/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/portal/apps/projects/views.py b/server/portal/apps/projects/views.py index e1ac3d179..e5b6246c1 100644 --- a/server/portal/apps/projects/views.py +++ b/server/portal/apps/projects/views.py @@ -94,12 +94,12 @@ def get(self, request, root_system=None): if query_string: search = IndexedProject.search() - ngram_query = Q("query_string", query=query_string, + ngram_query = Q("query_string", query=query_string.lower(), fields=["title", "id"], minimum_should_match='100%', default_operator='or') - wildcard_query = Q("wildcard", title=f'*{query_string}*') | Q("wildcard", id=f'*{query_string}*') + wildcard_query = Q("wildcard", title=f'*{query_string.lower()}*') | Q("wildcard", id=f'*{query_string.lower()}*') search = search.query(ngram_query | wildcard_query) search = search.extra(from_=int(offset), size=int(limit)) From 7787b4a344226b2688e0397202e132778f60e606 Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Tue, 10 Dec 2024 12:06:24 -0600 Subject: [PATCH 22/23] show toast notification when changing project owner (#1016) Co-authored-by: Shayan Khan --- client/src/redux/sagas/projects.sagas.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/src/redux/sagas/projects.sagas.js b/client/src/redux/sagas/projects.sagas.js index 452826253..ae4edc6da 100644 --- a/client/src/redux/sagas/projects.sagas.js +++ b/client/src/redux/sagas/projects.sagas.js @@ -144,6 +144,13 @@ export function* setMember(action) { type: 'PROJECTS_SET_MEMBER_SUCCESS', payload: metadata, }); + if (data.action === 'transfer_ownership') + yield put({ + type: 'ADD_TOAST', + payload: { + message: `Project ownership transferred to ${data.newOwner}.`, + }, + }); yield put({ type: 'PROJECTS_GET_LISTING', payload: { From 761c23d224b501f7d33752cccf30e831005b217d Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Tue, 10 Dec 2024 12:59:39 -0600 Subject: [PATCH 23/23] sort pulication requests in descending date order (#1014) Co-authored-by: Shayan Khan --- .../DataFilesPublicationRequestModal.jsx | 16 +++++- .../DescriptionList/DescriptionList.jsx | 53 +++++++++++++------ 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationRequestModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationRequestModal.jsx index 979b5f65b..87e8e97f4 100644 --- a/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationRequestModal.jsx +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationRequestModal.jsx @@ -13,10 +13,23 @@ const DataFilesPublicationRequestModal = () => { const { publicationRequests } = useSelector((state) => state.files.modalProps.publicationRequest) || []; + const compareFn = (req1, req2) => { + // sort more recent requests first + const date1 = new Date(req1.created_at); + const date2 = new Date(req2.created_at); + if (date1 < date2) { + return 1; + } + if (date1 > date2) { + return -1; + } + return 0; + }; + useEffect(() => { const data = {}; - publicationRequests?.forEach((request, index) => { + publicationRequests?.sort(compareFn).forEach((request, index) => { const publicationRequestDataObj = { Status: request.status, Reviewers: request.reviewers.reduce((acc, reviewer, index) => { @@ -27,6 +40,7 @@ const DataFilesPublicationRequestModal = () => { ); }, ''), Submitted: formatDateTime(new Date(request.created_at)), + _order: index, }; const heading = `Publication Request ${index + 1}`; diff --git a/client/src/components/_common/DescriptionList/DescriptionList.jsx b/client/src/components/_common/DescriptionList/DescriptionList.jsx index 86dc7a039..0cf30f56d 100644 --- a/client/src/components/_common/DescriptionList/DescriptionList.jsx +++ b/client/src/components/_common/DescriptionList/DescriptionList.jsx @@ -33,26 +33,45 @@ const DescriptionList = ({ className, data, density, direction }) => { shouldTruncateValues ? 'value-truncated' : '' }`; + const compareFn = (entry1, entry2) => { + const [, val1] = entry1; + const [, val2] = entry2; + if ((val1._order ?? 0) < (val2._order ?? 0)) { + return -1; + } + if ((val1._order ?? 0) > (val2._order ?? 0)) { + return 1; + } + return 0; + }; + return (
- {Object.entries(data).map(([key, value]) => ( - -
- {key} -
- {Array.isArray(value) ? ( - value.map((val) => ( -
- {val} + {Object.entries(data) + .sort(compareFn) + .filter(([key, _]) => !key.startsWith('_')) + .map(([key, value]) => ( + +
+ {key} +
+ {Array.isArray(value) ? ( + value.map((val) => ( +
+ {val} +
+ )) + ) : ( +
+ {value}
- )) - ) : ( -
- {value} -
- )} -
- ))} + )} + + ))}
); };