diff --git a/rdmo/projects/assets/js/common/utils/permissions.js b/rdmo/projects/assets/js/common/utils/permissions.js
new file mode 100644
index 0000000000..31857c7d0c
--- /dev/null
+++ b/rdmo/projects/assets/js/common/utils/permissions.js
@@ -0,0 +1,12 @@
+// combine permission sets, granting a permission when it is true in any set
+export const combinePermissions = (...permissionSets) => {
+ const effectivePermissions = {}
+
+ permissionSets.forEach((permissions) => {
+ Object.entries(permissions ?? {}).forEach(([key, value]) => {
+ effectivePermissions[key] = effectivePermissions[key] === true || value === true
+ })
+ })
+
+ return effectivePermissions
+}
diff --git a/rdmo/projects/assets/js/project/components/areas/Dashboard.js b/rdmo/projects/assets/js/project/components/areas/Dashboard.js
index b53c563b83..4c52d08c06 100644
--- a/rdmo/projects/assets/js/project/components/areas/Dashboard.js
+++ b/rdmo/projects/assets/js/project/components/areas/Dashboard.js
@@ -4,6 +4,7 @@ import { useDispatch, useSelector } from 'react-redux'
import * as configActions from 'rdmo/core/assets/js/actions/configActions'
import { navigateDashboard, updateProjectTask } from '../../actions/projectActions'
+import { usePermissions } from '../../hooks'
import { Tile } from '../helper'
import IssueDate from '../../../common/components/IssueDate'
@@ -17,7 +18,7 @@ const Dashboard = () => {
const dispatch = useDispatch()
const config = useSelector(state => state.config)
const settings = useSelector(state => state.settings)
- const perms = useSelector(state => state.project.project.project.permissions) ?? {}
+ const perms = usePermissions()
const allIssues = useSelector((state) => state.project.project.tasks) ?? []
/* Show only issues that resolve */
diff --git a/rdmo/projects/assets/js/project/components/areas/Information.js b/rdmo/projects/assets/js/project/components/areas/Information.js
index 6ba4630e3a..8879ca8144 100644
--- a/rdmo/projects/assets/js/project/components/areas/Information.js
+++ b/rdmo/projects/assets/js/project/components/areas/Information.js
@@ -1,6 +1,8 @@
import React from 'react'
import { useSelector } from 'react-redux'
+import { usePermissions } from '../../hooks'
+
import CopyProject from './information/CopyProject'
import HierarchyTree from './information/HierarchyTree'
import ProjectDelete from './information/ProjectDelete'
@@ -9,7 +11,7 @@ import ProjectVisibilityForm from './information/ProjectVisibilityForm'
const Information = () => {
const { hierarchy, project } = useSelector((state) => state.project.project) ?? {}
- const perms = project?.permissions ?? {}
+ const perms = usePermissions()
return (
diff --git a/rdmo/projects/assets/js/project/components/areas/Integrations.js b/rdmo/projects/assets/js/project/components/areas/Integrations.js
index 8faa7eb112..5d244b49be 100644
--- a/rdmo/projects/assets/js/project/components/areas/Integrations.js
+++ b/rdmo/projects/assets/js/project/components/areas/Integrations.js
@@ -3,13 +3,15 @@ import { useSelector } from 'react-redux'
import { useModal } from 'rdmo/core/assets/js/hooks'
+import { usePermissions } from '../../hooks'
+
import IntegrationsDropdown from '../helper/IntegrationsDropdown'
import IntegrationDeleteModal from './integrations/IntegrationDeleteModal'
import IntegrationModal from './integrations/IntegrationModal'
const Integrations = () => {
- const perms = useSelector((state) => state.project.project.project.permissions) ?? {}
+ const perms = usePermissions()
const providers = useSelector((state) => state.project.providers) ?? {}
const integrations = useSelector((state) => state.project.integrations) ?? []
diff --git a/rdmo/projects/assets/js/project/components/areas/Memberships.js b/rdmo/projects/assets/js/project/components/areas/Memberships.js
index bdfb74f960..d44e1c3728 100644
--- a/rdmo/projects/assets/js/project/components/areas/Memberships.js
+++ b/rdmo/projects/assets/js/project/components/areas/Memberships.js
@@ -4,15 +4,17 @@ import { isEmpty } from 'lodash'
import { useModal } from 'rdmo/core/assets/js/hooks'
+import { usePermissions } from '../../hooks'
+
import MembershipInviteModal from './memberships/MembershipInviteModal'
import MembershipTable from './memberships/MembershipTable'
const Memberships = () => {
const { show: showInvite, open: openInvite, close: closeInvite } = useModal()
- const { memberships, project } = useSelector((state) => state.project.project) ?? {}
+ const { memberships } = useSelector((state) => state.project.project) ?? {}
const { invites } = useSelector((state) => state.project)
- const perms = project?.permissions ?? {}
+ const perms = usePermissions()
return (
<>
diff --git a/rdmo/projects/assets/js/project/components/areas/Snapshots.js b/rdmo/projects/assets/js/project/components/areas/Snapshots.js
index 8b19c71c9e..59c5f9beca 100644
--- a/rdmo/projects/assets/js/project/components/areas/Snapshots.js
+++ b/rdmo/projects/assets/js/project/components/areas/Snapshots.js
@@ -3,14 +3,16 @@ import { useSelector } from 'react-redux'
import { useModal } from 'rdmo/core/assets/js/hooks'
+import { usePermissions } from '../../hooks'
+
import SnapshotModal from './snapshots/SnapshotModal'
import SnapshotTable from './snapshots/SnapshotTable'
const Snapshots = () => {
const { show: showSnapshot, open: openSnapshot, close: closeSnapshot } = useModal()
- const { snapshots, project } = useSelector((state) => state.project.project) ?? {}
- const perms = project?.permissions ?? {}
+ const { snapshots } = useSelector((state) => state.project.project) ?? {}
+ const perms = usePermissions()
return (
diff --git a/rdmo/projects/assets/js/project/components/areas/information/ProjectForm.js b/rdmo/projects/assets/js/project/components/areas/information/ProjectForm.js
index e74ea1cd5c..e2f530bfec 100644
--- a/rdmo/projects/assets/js/project/components/areas/information/ProjectForm.js
+++ b/rdmo/projects/assets/js/project/components/areas/information/ProjectForm.js
@@ -13,7 +13,7 @@ import Html from 'rdmo/core/assets/js/components/Html'
import { copyProject, createProject } from '../../../../projects/actions/projectsActions'
import { updateProject } from '../../../actions/projectActions'
-import { useFieldErrors } from '../../../hooks/useFieldErrors'
+import { useFieldErrors } from '../../../hooks'
import ProjectApi from '../../../api/ProjectApi'
@@ -265,7 +265,7 @@ const ProjectForm = ({
}
{
- unavailableCatalog && project.permissions.can_change_project &&
+ unavailableCatalog && !disabled &&
}
diff --git a/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationDeleteModal.js b/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationDeleteModal.js
index 5e809bd38f..45bda5ea96 100644
--- a/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationDeleteModal.js
+++ b/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationDeleteModal.js
@@ -7,7 +7,7 @@ import { Modal } from 'rdmo/core/assets/js/components'
import Html from 'rdmo/core/assets/js/components/Html'
import { clearProjectErrors, deleteProjectIntegration } from '../../../actions/projectActions'
-import { useFieldErrors } from '../../../hooks/useFieldErrors'
+import { useFieldErrors } from '../../../hooks'
const IntegrationDeleteModal = ({ show, onClose, integration }) => {
const dispatch = useDispatch()
diff --git a/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationModal.js b/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationModal.js
index d34c67f72f..4955899b78 100644
--- a/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationModal.js
+++ b/rdmo/projects/assets/js/project/components/areas/integrations/IntegrationModal.js
@@ -10,7 +10,7 @@ import {
createProjectIntegration,
updateProjectIntegration
} from '../../../actions/projectActions'
-import { useFieldErrors } from '../../../hooks/useFieldErrors'
+import { useFieldErrors } from '../../../hooks'
import IntegrationSecretField from './IntegrationSecretField'
diff --git a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipDeleteModal.js b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipDeleteModal.js
index 3ec975e08f..519306544f 100644
--- a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipDeleteModal.js
+++ b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipDeleteModal.js
@@ -6,7 +6,7 @@ import Html from 'rdmo/core/assets/js/components/Html'
import Modal from 'rdmo/core/assets/js/components/Modal'
import { deleteProjectInvite, deleteProjectMember, leaveProject } from '../../../actions/projectActions'
-import { useFieldErrors } from '../../../hooks/useFieldErrors'
+import { useFieldErrors } from '../../../hooks'
const MembershipDeleteModal = ({
type, show, person, onClose, isAdminOrSiteManager = false,
diff --git a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipInviteModal.js b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipInviteModal.js
index 3799c8c4cb..943c43b01d 100644
--- a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipInviteModal.js
+++ b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipInviteModal.js
@@ -8,16 +8,15 @@ import { Modal, Tooltip } from 'rdmo/core/assets/js/components'
import Html from 'rdmo/core/assets/js/components/Html'
import { clearProjectErrors, createProjectMember, sendProjectInvite } from '../../../actions/projectActions'
-import { useFieldErrors } from '../../../hooks/useFieldErrors'
+import { useFieldErrors, usePermissions } from '../../../hooks'
const initialForm = { lookup: '', role: 'author' }
const MembershipInviteModal = ({ show, onClose }) => {
const dispatch = useDispatch()
const templates = useSelector((state) => state.templates)
- const { project } = useSelector((state) => state.project.project) || {}
const errors = useFieldErrors()
- const perms = project?.permissions || {}
+ const perms = usePermissions()
const roleOptions = useSelector((state) => state.roles?.roles) || []
const [formData, setFormData] = useState(initialForm)
diff --git a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipTable.js b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipTable.js
index 7de7315db1..d7c25125de 100644
--- a/rdmo/projects/assets/js/project/components/areas/memberships/MembershipTable.js
+++ b/rdmo/projects/assets/js/project/components/areas/memberships/MembershipTable.js
@@ -8,14 +8,14 @@ import { useModal } from 'rdmo/core/assets/js/hooks'
import Select from 'rdmo/core/assets/js/components/Select'
import { updateProjectInvite, updateProjectMember } from '../../../actions/projectActions'
+import { usePermissions } from '../../../hooks'
import MembershipDeleteModal from './MembershipDeleteModal'
const MembershipTable = ({ persons, type }) => {
const dispatch = useDispatch()
const currentUser = useSelector((state) => state.user.currentUser)
- const { project } = useSelector((state) => state.project.project) || {}
- const perms = project?.permissions || {}
+ const perms = usePermissions()
const roleOptions = useSelector((state) => state.roles?.roles) || []
const { show: showConfirm, open: openConfirm, close: closeConfirm } = useModal()
const [modalState, setModalState] = useState(null)
diff --git a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotDeleteModal.js b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotDeleteModal.js
index 4f78a6e55e..687f9eacbf 100644
--- a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotDeleteModal.js
+++ b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotDeleteModal.js
@@ -6,7 +6,7 @@ import Html from 'rdmo/core/assets/js/components/Html'
import Modal from 'rdmo/core/assets/js/components/Modal'
import { deleteSnapshot } from '../../../actions/projectActions'
-import { useFieldErrors } from '../../../hooks/useFieldErrors'
+import { useFieldErrors } from '../../../hooks'
const SnapshotDeleteModal = ({ show, onClose, snapshot }) => {
const dispatch = useDispatch()
diff --git a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotModal.js b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotModal.js
index 9235fa65d3..29956ff8cb 100644
--- a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotModal.js
+++ b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotModal.js
@@ -8,7 +8,7 @@ import { Input, Textarea } from 'rdmo/core/assets/js/components/forms'
import Html from 'rdmo/core/assets/js/components/Html'
import { clearProjectErrors, createSnapshot, updateSnapshot } from '../../../actions/projectActions'
-import { useFieldErrors } from '../../../hooks/useFieldErrors'
+import { useFieldErrors } from '../../../hooks'
const initialForm = { title: '', description: '' }
diff --git a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotRollbackModal.js b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotRollbackModal.js
index 1b9bc15a8f..f222ad804c 100644
--- a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotRollbackModal.js
+++ b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotRollbackModal.js
@@ -6,7 +6,7 @@ import Html from 'rdmo/core/assets/js/components/Html'
import Modal from 'rdmo/core/assets/js/components/Modal'
import { rollbackSnapshot } from '../../../actions/projectActions'
-import { useFieldErrors } from '../../../hooks/useFieldErrors'
+import { useFieldErrors } from '../../../hooks'
const SnapshotRollbackModal = ({ show, onClose, snapshot }) => {
const dispatch = useDispatch()
diff --git a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotTable.js b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotTable.js
index 9245b27772..8bc010ab2e 100644
--- a/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotTable.js
+++ b/rdmo/projects/assets/js/project/components/areas/snapshots/SnapshotTable.js
@@ -1,12 +1,13 @@
import React, { useState } from 'react'
import PropTypes from 'prop-types'
-import { useDispatch, useSelector } from 'react-redux'
+import { useDispatch } from 'react-redux'
import { Link } from 'rdmo/core/assets/js/components'
import { useModal } from 'rdmo/core/assets/js/hooks'
import { formatDateTime } from 'rdmo/core/assets/js/utils/date'
import { navigateDashboard } from '../../../actions/projectActions'
+import { usePermissions } from '../../../hooks'
import { buildPath } from '../../../utils/location'
import SnapshotDeleteModal from './SnapshotDeleteModal'
@@ -15,8 +16,7 @@ import SnapshotRollbackModal from './SnapshotRollbackModal'
const SnapshotTable = ({ snapshots, onCreate}) => {
const dispatch = useDispatch()
- const { project } = useSelector((state) => state.project.project) || {}
- const perms = project?.permissions || {}
+ const perms = usePermissions()
const updateModal = useModal()
const rollbackModal = useModal()
diff --git a/rdmo/projects/assets/js/project/hooks/index.js b/rdmo/projects/assets/js/project/hooks/index.js
new file mode 100644
index 0000000000..75cbdd359b
--- /dev/null
+++ b/rdmo/projects/assets/js/project/hooks/index.js
@@ -0,0 +1,2 @@
+export { useFieldErrors } from './useFieldErrors'
+export { usePermissions } from './usePermissions'
diff --git a/rdmo/projects/assets/js/project/hooks/usePermissions.js b/rdmo/projects/assets/js/project/hooks/usePermissions.js
new file mode 100644
index 0000000000..4f132c522b
--- /dev/null
+++ b/rdmo/projects/assets/js/project/hooks/usePermissions.js
@@ -0,0 +1,19 @@
+import { useMemo } from 'react'
+import { useSelector } from 'react-redux'
+
+import { combinePermissions } from '../../common/utils/permissions'
+
+// return the combined model-based permissions for the current user and object-based permissions for the current project
+export const usePermissions = () => {
+ const projectPermissions = useSelector(
+ (state) => state.project.project?.project?.permissions
+ )
+ const userPermissions = useSelector(
+ (state) => state.user.currentUser?.permissions
+ )
+
+ return useMemo(
+ () => combinePermissions(projectPermissions, userPermissions),
+ [projectPermissions, userPermissions]
+ )
+}
diff --git a/rdmo/projects/assets/js/project/store/configureStore.js b/rdmo/projects/assets/js/project/store/configureStore.js
index 05e88b3813..3b1a2bec58 100644
--- a/rdmo/projects/assets/js/project/store/configureStore.js
+++ b/rdmo/projects/assets/js/project/store/configureStore.js
@@ -20,7 +20,7 @@ import { checkStoreId } from 'rdmo/core/assets/js/utils/store'
import * as rolesActions from '../../common/actions/rolesActions'
import rolesReducer from '../../common/reducers/rolesReducer'
-// import { getEffectivePermissions } from '../../common/utils/permissions'
+import { combinePermissions } from '../../common/utils/permissions'
import * as projectActions from '../actions/projectActions'
import projectReducer from '../reducers/projectReducer'
import { parseLocation } from '../utils/location'
@@ -91,22 +91,20 @@ export default function configureStore() {
const project = state.project.project.project
const currentUser = state.user.currentUser
- const permissions = project.permissions
-
- // const permissions = getEffectivePermissions(
- // project.permissions,
- // currentUser.permissions
- // )
+ const permissions = combinePermissions(
+ project.permissions,
+ currentUser.permissions
+ )
if (permissions.can_view_invite) {
store.dispatch(projectActions.fetchProjectInvites(projectId))
}
if (permissions.can_view_visibility) {
- if (state.settings.multisite && currentUser.permissions.can_view_site) {
+ if (state.settings.multisite && permissions.can_view_site) {
store.dispatch(sitesActions.fetchSites())
}
- if (state.settings.groups && currentUser.permissions.can_view_group) {
+ if (state.settings.groups && permissions.can_view_group) {
store.dispatch(groupsActions.fetchGroups())
}
if (!isNil(project.visibility)) {
diff --git a/rdmo/projects/assets/js/projects/components/Main.js b/rdmo/projects/assets/js/projects/components/Main.js
index c65661603b..659faa369e 100644
--- a/rdmo/projects/assets/js/projects/components/Main.js
+++ b/rdmo/projects/assets/js/projects/components/Main.js
@@ -9,6 +9,7 @@ import { useModal, useScrollToTop } from 'rdmo/core/assets/js/hooks'
import { formatDateTime } from 'rdmo/core/assets/js/utils/date'
import { baseUrl } from 'rdmo/core/assets/js/utils/meta'
+import { combinePermissions } from '../../common/utils/permissions'
import * as projectsActions from '../actions/projectsActions'
import { HEADER_FORMATTERS, SORTABLE_COLUMNS } from '../utils'
@@ -39,6 +40,7 @@ const Main = () => {
if (!projectsObject.ready) return null
const { allowedTypes, catalogs, importUrls, invites, projects, projectsCount, hasNext } = projectsObject
const { currentUser } = currentUserObject
+ const userPerms = currentUser.permissions ?? {}
const { myProjects } = config
const invitationsModalProps = {
@@ -266,7 +268,7 @@ const Main = () => {
created: content => formatDateTime(content, 'long'),
last_changed: content => formatDateTime(content, 'long'),
actions: (_content, row) => {
- const perms = row.permissions || {}
+ const perms = combinePermissions(row.permissions, userPerms)
return (