diff --git a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx index 636a4e5f7..010d88065 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 }); @@ -183,9 +183,7 @@ const DataFilesBreadcrumbs = ({
{currentDirectory.length === 0 ? ( - - {truncateMiddle(systemName || 'Shared Workspaces', 30)} - + {truncateMiddle(systemName, 30)} ) : ( currentDirectory.map((pathComp, i) => { if (i === fullPath.length - 1) { @@ -194,11 +192,13 @@ const DataFilesBreadcrumbs = ({ }) )}
- {systemName && api === 'tapis' && ( - - )} + {systemName && + api === 'tapis' && + !isRootProjectSystem(selectedSystem ?? '') && ( + + )}
); }; diff --git a/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx index 60e90fce2..33affeef4 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 (
{ const dispatch = useDispatch(); @@ -40,6 +41,12 @@ const DataFilesManageProjectModal = () => { return projectSystem?.readOnly || !canEditSystem; }); + const portalName = useSelector((state) => state.workbench.portalName); + + const { DataFilesManageProjectModalAddon } = useAddonComponents({ + portalName, + }); + const toggle = useCallback(() => { setTransferMode(false); dispatch({ @@ -154,6 +161,9 @@ const DataFilesManageProjectModal = () => { ) : null}
+ {DataFilesManageProjectModalAddon && ( + + )} 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/DataFilesProjectEditDescriptionModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesProjectEditDescriptionModal.jsx index 8172e234d..03b5aa54c 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', }, }); }, @@ -89,7 +90,7 @@ const DataFilesProjectEditDescriptionModal = () => { {({ isValid, dirty }) => (
- Edit Project + Edit Dataset { + 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; 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..dbd530a02 --- /dev/null +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesPublicationAuthorsModal.module.scss @@ -0,0 +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%; + } +} 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/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx index 0b49d5840..cb3fe5d44 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,14 @@ 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/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx b/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx index 25467417d..cde79cdee 100644 --- a/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx +++ b/client/src/components/DataFiles/DataFilesProjectFileListing/DataFilesProjectFileListing.jsx @@ -8,18 +8,18 @@ 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,11 +44,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( (state) => state.files.folderMetadata?.FilesListing @@ -130,14 +125,16 @@ const DataFilesProjectFileListing = ({ rootSystem, system, path }) => { {canEditSystem ? ( <> | ) : null} - + {!isPublicationSystem(rootSystem) && !isReviewSystem(rootSystem) && ( + + )} {DataFilesProjectFileListingAddon && ( { folderMetadata={folderMetadata} metadata={metadata} path={path} - showCitation={isPublicationSystem} + showCitation={isPublicationSystem(rootSystem)} /> ) : ( 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/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} -
- )} -
- ))} + )} + + ))}
); }; 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/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx b/client/src/components/_custom/drp/DataFilesProjectFileListingAddon/DataFilesProjectFileListingAddon.jsx index 328f14df8..a2099a34c 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,18 @@ const DataFilesProjectFileListingAddon = ({ rootSystem, system }) => { return ( <> + {(isPublicationSystem(rootSystem) || isReviewSystem(rootSystem)) && ( + <> + + + )} {canEditDataset && ( <> | 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/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/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/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/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationRequest.jsx b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationRequest.jsx index 3ccff7617..16eda7631 100644 --- a/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationRequest.jsx +++ b/client/src/components/_custom/drp/DataFilesProjectPublish/DataFilesProjectPublishWizardSteps/SubmitPublicationRequest.jsx @@ -85,7 +85,8 @@ 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.
diff --git a/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js b/client/src/components/_custom/drp/utils/hooks/useDrpDatasetModals.js index 6ce6f46ae..2ba2ecab0 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/useAddonComponents.js b/client/src/hooks/datafiles/useAddonComponents.js index cce3871ba..83c9e25a2 100644 --- a/client/src/hooks/datafiles/useAddonComponents.js +++ b/client/src/hooks/datafiles/useAddonComponents.js @@ -1,30 +1,46 @@ -import { useState, useEffect } from 'react'; +import { useState, useEffect, useMemo } from 'react'; import { useSelector } from 'react-redux'; const useAddonComponents = ({ portalName }) => { const [addonComponents, setAddonComponents] = useState({}); const addons = useSelector((state) => state.workbench.config.addons); + const { metadata } = useSelector((state) => state.projects); + + const shouldLoadComponents = useMemo( + () => addons && metadata.projectId !== 'community', + [addons, metadata.projectId] + ); + useEffect(() => { + if (!shouldLoadComponents) { + setAddonComponents({}); + return; + } + const loadAddonComponents = async () => { try { - for (const addonName of addons) { - const module = await import( - `../../components/_custom/${portalName.toLowerCase()}/${addonName}/${addonName}.jsx` - ); - setAddonComponents((prevComponents) => ({ - ...prevComponents, - [addonName]: module.default, - })); - } + const modules = await Promise.all( + addons.map((addonName) => + import( + `../../components/_custom/${portalName.toLowerCase()}/${addonName}/${addonName}.jsx` + ) + ) + ); + + const components = modules.reduce((acc, module, index) => { + acc[addons[index]] = module.default; + return acc; + }, {}); + + setAddonComponents(components); } catch (error) { console.error('Error loading addon components:', error); } }; - if (addons) { - loadAddonComponents(); - } - }, []); + loadAddonComponents(); + }, [shouldLoadComponents, portalName]); + return addonComponents; }; diff --git a/client/src/hooks/datafiles/useSystems.js b/client/src/hooks/datafiles/useSystems.js index c5fa5a65f..0527c377f 100644 --- a/client/src/hooks/datafiles/useSystems.js +++ b/client/src/hooks/datafiles/useSystems.js @@ -29,7 +29,34 @@ function useSystems() { [data] ); - return { data, loading, error, fetchSystems, fetchSelectedSystem }; + const isRootProjectSystem = useCallback(({ system = '' }) => { + return data.some((s) => s.scheme === 'projects' && s.system === system); + }); + + 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, + 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..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, }, @@ -221,6 +222,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/datafiles.sagas.js b/client/src/redux/sagas/datafiles.sagas.js index 0a4f504d9..ca58fe142 100644 --- a/client/src/redux/sagas/datafiles.sagas.js +++ b/client/src/redux/sagas/datafiles.sagas.js @@ -508,7 +508,9 @@ export async function uploadFileUtil( formData.append('uploaded_file', file); formData.append( 'metadata', - metadata ? JSON.stringify({ data_type: 'file', ...metadata }) : null + metadata && !system.includes('community') + ? JSON.stringify({ data_type: 'file', ...metadata }) + : null ); const url = removeDuplicateSlashes( diff --git a/client/src/redux/sagas/projects.sagas.js b/client/src/redux/sagas/projects.sagas.js index 12cd8d1bd..ae4edc6da 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', }); @@ -133,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: { @@ -164,7 +182,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', @@ -172,7 +190,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/client/src/redux/sagas/publications.sagas.js b/client/src/redux/sagas/publications.sagas.js index e4f32db23..355a4c450 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..594fe8dcf 100644 --- a/client/src/utils/systems.js +++ b/client/src/utils/systems.js @@ -92,7 +92,7 @@ export function findSystemOrProjectDisplayName( let project = findProjectTitle(projectsList, system, projectTitle); if (!project) { const projectSystem = systemList.find( - (system) => system.scheme === 'projects' + (sys) => sys.scheme === 'projects' && sys.system === system ); return projectSystem ? projectSystem.name : ''; } else { 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/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)) 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')) 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..1cd37ac71 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(self, project_id): + """ + Alert project authors that their request has been accepted. + """ + user_emails = get_project_user_emails(project_id) + 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(self, project_id: str): + """ + Alert project authors that their request has been rejected. + """ + user_emails = get_project_user_emails(project_id) + 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/projects/workspace_operations/shared_workspace_operations.py b/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py index 347e976eb..062d48069 100644 --- a/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py +++ b/server/portal/apps/projects/workspace_operations/shared_workspace_operations.py @@ -339,13 +339,33 @@ def list_projects(client, root_system_id=None): if root_system: query += f"~(rootDir.like.{root_system['rootDir']}*)" + 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') + + if community_system and not is_review_system and not is_publication_system: + community_data_query = f"(id.like.{settings.PORTAL_PROJECTS_SYSTEM_PREFIX}.*)~(rootDir.like.{community_system['homeDir']}*)" + else: + community_data_query = None + + # use limit as -1 to allow search to corelate with # all projects available to the api user listing = client.systems.getSystems(listType='ALL', search=query, select=fields, limit=-1) - + if community_data_query: + community_listing = client.systems.getSystems(listType='ALL', + search=community_data_query, + select=fields, + limit=-1) + listing = community_listing + listing + serialized_listing = map(lambda prj: { "id": prj.id, "path": prj.rootDir, diff --git a/server/portal/apps/publications/views.py b/server/portal/apps/publications/views.py index 55aeef777..f4f17397c 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,9 @@ def post(self, request): if not full_project_id: raise ApiException("Missing project ID", status=400) + 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] else: @@ -314,6 +317,9 @@ def post(self, request): if not full_project_id: raise ApiException("Missing project ID", status=400) + 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) # Create notification diff --git a/server/portal/libs/agave/operations.py b/server/portal/libs/agave/operations.py index c957b5bd6..761f63dbe 100644 --- a/server/portal/libs/agave/operations.py +++ b/server/portal/libs/agave/operations.py @@ -507,10 +507,16 @@ def trash(client, system, path, homeDir, metadata=None): if err.response.status_code != 404: logger.error(f'Unexpected exception listing .trash path in {system}') raise - trash_entity = create_entity_metadata(system, constants.TRASH, {}) - add_node_to_project(system, 'NODE_ROOT', trash_entity.uuid, trash_entity.name, settings.TAPIS_DEFAULT_TRASH_NAME) mkdir(client, system, homeDir, settings.TAPIS_DEFAULT_TRASH_NAME) + try: + trash_entity = get_entity(system, f'{settings.TAPIS_DEFAULT_TRASH_NAME}') + if not trash_entity: + new_entity = create_entity_metadata(system, constants.TRASH, {}) + add_node_to_project(system, 'NODE_ROOT', new_entity.uuid, new_entity.name, settings.TAPIS_DEFAULT_TRASH_NAME) + except Exception as e: + print(f'Error creating trash entity: {e}') + resp = move(client, system, path, system, f'{homeDir}/{settings.TAPIS_DEFAULT_TRASH_NAME}', file_name, metadata) 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 diff --git a/server/portal/settings/settings_default.py b/server/portal/settings/settings_default.py index eb12d24d9..6ba54927f 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', @@ -103,25 +94,34 @@ 'siteSearchPriority': 0 }, { - 'name': 'Projects', + "name": "Community Data", + "system": "cloud.data", + "scheme": "community", + "api": "tapis", + "homeDir": "/corral-repl/utexas/OTH21076/data_pprd/community", + "icon": None, + "siteSearchPriority": 0, + }, + { + 'name': 'Dataset', 'scheme': 'projects', 'api': 'tapis', 'icon': 'publications', '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', + { + 'name': 'Published Datasets', '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' @@ -255,6 +255,7 @@ """ _WORKBENCH_SETTINGS = { "debug": _DEBUG, + "canPublish": True, "makeLink": False, "viewPath": True, "compressApp": 'compress', @@ -272,7 +273,8 @@ "hasCustomDataFilesToolbarChecks": True, "addons": ['DataFilesProjectFileListingAddon', 'DataFilesAddProjectModalAddon', 'DataFilesProjectEditDescriptionModalAddon', 'DataFilesProjectFileListingMetadataAddon', 'DataFilesProjectFileListingMetadataTitleAddon', - 'DataFilesUploadModalAddon', 'DataFilesPreviewModalAddon', 'DataFilesProjectPublish', 'DataFilesProjectReview'], + 'DataFilesUploadModalAddon', 'DataFilesPreviewModalAddon', 'DataFilesProjectPublish', 'DataFilesProjectReview', + 'DataFilesManageProjectModalAddon'], "showDataFileType": True, "onboardingCompleteRedirect": '/workbench/', "noPHISystem": "",