diff --git a/.circleci/config.yml b/.circleci/config.yml index f4215a9be..0c5bd0d44 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -138,7 +138,7 @@ workflows: - build-dev filters: branches: - only: ['feature/faqs', 'feature/project_september'] + only: ['feature/unified-permissions'] - deployProd: context : org-global diff --git a/docs/permissions-guide/permissions-guide.md b/docs/permissions-guide/permissions-guide.md index cb4134694..b6d53a136 100644 --- a/docs/permissions-guide/permissions-guide.md +++ b/docs/permissions-guide/permissions-guide.md @@ -18,12 +18,12 @@ Let's say you would like to add a new place in code where you want to check user 2. After you add a new permission, regenerate [permissions list](https://htmlpreview.github.io/?https://github.com/appirio-tech/connect-app/blob/dev/docs/permissions.html) by running `npm run generate:doc:permissions`. -3. To check if user has permission in code use method `hasPermission(permission)`. +3. To check if logged-in user has permission in code use method `hasPermission(permission)`. Example:
- + ```js - import PERMISSIONS from 'config/permissions' + import { PERMISSIONS } from 'config/permissions' import { hasPermission } from 'helpers/permissions' if (hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN)) { @@ -31,7 +31,10 @@ Let's say you would like to add a new place in code where you want to check user } ``` - - Note, optionally, you may pass the `project` object like this `hasPermission(permission, project)`. But you don't have to as `hasPermission` gets `project` object from the Redux Store (`projectState.project`) automatically. Only in case if you want to check user permission to another project which is not loaded into the Redux Store then you may pass `project` explicitly. +4. If you would like to check permissions for other user (not the current user) or for other project (not the current project) you may pass the second argument `entities: { project?: object, user?: object }`: + - `hasPermission(permission, { project })` - check permissions for another project + - `hasPermission(permission, { user })` - check permissions for another user + - `hasPermission(permission, { project, user })` - check permissions for another project and user ## Roles @@ -49,4 +52,4 @@ By default every user has one role `Topcoder User`, generally this means that su When user joins some project and become a member of the project, such a user has one **Project Role** inside that project. One user may have different **Project Role** in different projects. See [the list of all Project Roles](https://github.com/appirio-tech/connect-app/blob/dev/src/config/constants.js#L638-L647) which we use in Connect App. - \ No newline at end of file + diff --git a/docs/permissions.html b/docs/permissions.html index 65e337cbd..44dfaff34 100644 --- a/docs/permissions.html +++ b/docs/permissions.html @@ -6,10 +6,53 @@ Permissions @@ -21,421 +64,1135 @@

Permissions

Legend:

-

Project Plan

+

+ Project Plan +

- Manage project plan -
MANAGE_PROJECT_PLAN
+
+ Manage project plan +
+
MANAGE_PROJECT_PLAN
Create, edit and delete phases and milestones.
- manager - project_manager - program_manager - solution_architect - copilot - administrator - Connect Admin +
+ copilot + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
+
+
+
+
+
+ Manage asset libraries files and links (not own) +
+
MANAGE_NOT_OWN_ATTACHEMENT
+
+
+
+
+
+ +
+ administrator + Connect Admin +
+
+
+
+
+
+ Manage completed phases +
+
MANAGE_COMPLETED_PHASE
+
+
+
+
+
+ +
+ administrator + Connect Admin +
- Manage asset libraries files and links -
MANAGE_NOT_OWN_ATTACHEMENT
+
+ Expand active phases by default +
+
EXPAND_ACTIVE_PHASES_BY_DEFAULT
- administrator - Connect Admin +
+ customer +
+ +
+
- Manage completed phases -
MANAGE_COMPLETED_PHASE
+
+ View draft phases +
+
VIEW_DRAFT_PHASES
- administrator - Connect Admin +
+ copilot + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ Connect Manager + administrator + Connect Admin +
-

Project Members

+

+ Project Members +

- Manage topcoder team -
MANAGE_TOPCODER_TEAM
-
Invite new members or delete them. There are some additional restrictions for some roles.
+
+ Manage Customer Team +
+
MANAGE_CUSTOMER_TEAM
+
Invite or cancel invitations and remove members in the Customer Team.
+
+
+
+ customer + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
+
+
+
+
+
+ Manage Copilots +
+
MANAGE_COPILOTS
+
Directly invite copilots to the project.
+
+
+
+
+ +
+ administrator + Connect Admin + Connect Copilot Manager +
+
+
+
+
+
+ Manage Topcoder Team +
+
MANAGE_TOPCODER_TEAM
+
Invite or cancel invitations and remove members in the Topcoder Team.
- manager - account_manager - account_executive - project_manager - program_manager - solution_architect - administrator - Connect Admin +
+ manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
- Join topcoder team -
JOIN_TOPCODER_TEAM
+
+ Join Topcoder Team +
+
JOIN_TOPCODER_TEAM
Join Topcoder Team without invitation
- administrator - Connect Admin - Connect Manager +
+
+ +
+ administrator + Connect Admin + Connect Manager +
- Manage copilots -
MANAGE_COPILOTS
-
Directly invite copilots to the project.
+
+ Be listed in Customer Team +
+
BE_LISTED_IN_CUSTOMER_TEAM
+
Who should be listed in Customer Team.
- administrator - Connect Admin - Connect Copilot Manager +
+ customer +
+ +
+
- Request copilots -
REQUEST_COPILOTS
+
+ Be listed in Copilot Team +
+
BE_LISTED_IN_COPILOT_TEAM
+
Who should be listed in Copilot Team.
+
+
+
+ copilot +
+ +
+
+
+
+
+
+
+ Be listed in Topcoder Team +
+
BE_LISTED_IN_TOPCODER_TEAM
+
Who should be listed in Topcoder Team.
+
+
+
+ manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+
+
+
+
+
+
+ Request copilots +
+
REQUEST_COPILOTS
Request copilots to the project.
- manager - account_manager - account_executive - project_manager - program_manager - solution_architect - administrator - Connect Admin - Connect Copilot Manager +
+ manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin + Connect Copilot Manager +
+
+
+
+
+
+ See Member Suggestions +
+
SEE_MEMBER_SUGGESTIONS
+
When entering user handle in the invite field.
+
+
+
+
+ +
+ administrator + Connect Admin + Connect Manager + Connect Account Manager + Connect Copilot Manager +
-

Topics & Posts

+

+ Topics & Posts +

- Access private posts -
ACCESS_PRIVATE_POST
+
+ Access private posts +
+
ACCESS_PRIVATE_POST
- copilot - administrator - Connect Admin - Connect Manager - Program Manager - Solution Architect - Project Manager +
+ copilot + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
+
+
+
+
+
+ Create topics +
+
CREATE_TOPICS
+
Create threads (supported only for old messages tab at the moment)
+
+
+
+
+ +
+ Topcoder User + administrator + Connect Admin + Connect Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
+
+
+
+
+
+ Create posts +
+
CREATE_POSTS
+
Comment/post in already created threads/topics
+
+
+
+
+ +
+ Topcoder User + administrator + Connect Admin + Connect Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
-

Budget & Invoice Reports

+

+ Budget & Invoice Reports +

- Access budget report -
ACCESS_BUDGET_REPORT
+
+ Access budget report +
+
ACCESS_BUDGET_REPORT
- customer - manager - account_manager - account_executive - project_manager - program_manager - solution_architect - administrator - Connect Admin +
+ customer + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
- Access budget spent report -
ACCESS_BUDGET_SPENT_REPORT
+
+ Access budget spent report +
+
ACCESS_BUDGET_SPENT_REPORT
- manager - account_manager - account_executive - project_manager - program_manager - solution_architect - administrator - Connect Admin +
+ manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
- Access invoice report -
ACCESS_INVOICE_REPORT
+
+ Access invoice report +
+
ACCESS_INVOICE_REPORT
- customer - manager - account_manager - account_executive - project_manager - program_manager - solution_architect - administrator - Connect Admin +
+ customer + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
-

User Profile

+

+ User Settings +

- Update phone number in user profile -
UPDATE_USER_PROFILE_PHONE
+
+ Update phone number in user profile +
+
UPDATE_USER_PROFILE_PHONE
- Topcoder User - administrator - Connect Admin - Connect Manager - Connect Copilot Manager - Connect Account Manager - Business Development Representative - Presales - Account Executive - Program Manager - Solution Architect - Project Manager - Connect Copilot +
+
+ +
+ Topcoder User + administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
- Update company name in user profile -
UPDATE_USER_PROFILE_COMPANY
+
+ Update company name in user profile +
+
UPDATE_USER_PROFILE_COMPANY
- administrator - Connect Admin - Connect Manager - Connect Copilot Manager - Connect Account Manager - Business Development Representative - Presales - Account Executive - Program Manager - Solution Architect - Project Manager +
+
+ +
+ administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager +
- View User Profile as Copilot -
VIEW_USER_PROFILE_AS_COPILOT
+
+ View User Profile as Copilot +
+
VIEW_USER_PROFILE_AS_COPILOT
- Connect Copilot +
+
+ +
+ Connect Copilot +
- View User Profile as Topcoder Employee -
VIEW_USER_PROFILE_AS_TOPCODER_EMPLOYEE
+
+ View User Profile as Topcoder Employee +
+
VIEW_USER_PROFILE_AS_TOPCODER_EMPLOYEE
- administrator - Connect Admin - Connect Manager - Connect Copilot Manager - Connect Account Manager - Business Development Representative - Presales - Account Executive - Program Manager - Solution Architect - Project Manager +
+
+ +
+ administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager +
- View User Profile as Customer -
VIEW_USER_PROFILE_AS_CUSTOMER
+
+ View User Profile as Customer +
+
VIEW_USER_PROFILE_AS_CUSTOMER
- Topcoder User - administrator - Connect Admin - Connect Manager - Connect Copilot Manager - Connect Account Manager - Business Development Representative - Presales - Account Executive - Program Manager - Solution Architect - Project Manager - Connect Copilot +
+
+ +
+ Topcoder User + administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
+
+
+
+
+
+ Update User Email +
+
UPDATE_USER_EMAIL
+
+
+
+
+
+ +
+ Topcoder User + administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
+
+
+
+
+
+ Enable/disable website notifications. +
+
TOGGLE_WEBSITE_NOTIFICATIONS
+
+
+
+
+
+ +
+ administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
-

View Member Suggestions

+

+ Project List +

- Member Suggestions -
SEE_MEMBER_SUGGESTIONS
+
+ See My Projects Filter +
+
SEE_MY_PROJECTS_FILTER
+
+
+
+
+
+ +
+ administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager +
+
+
+
+
+
+ Search Project +
+
SEARCH_PROJECTS
+
+
+
+
+
+ +
+ administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
+
+
+
+
+
+ See Walk Through +
+
SEE_WALK_THROUGH
- administrator - Connect Admin - Connect Manager - Connect Account Manager - Connect Copilot Manager +
+
+ +
+ Topcoder User + administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
+
+
+
+
+
+ Have Grid View by default +
+
SEE_GRID_VIEW_BY_DEFAULT
+
+
+
+
+
+ +
+ administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
+
+
+
+
+
+ Retry project loading +
+
RETRY_PROJECTS_LOADING
+
+
+
+
+
+ +
+ Topcoder User + administrator + Connect Admin + Connect Manager + Connect Copilot Manager + Connect Account Manager + Business Development Representative + Presales + Account Executive + Program Manager + Solution Architect + Project Manager + Connect Copilot +
-

My Projects Filter

+

+ Project Details +

- My Projects Filter -
SEE_MY_PROJECTS_FILTER
+
+ Edit project specification +
+
EDIT_PROJECT_SPECIFICATION
+
+
+
+
+ customer + copilot + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
+
+
+
+
+
+ Manage project assets +
+
MANAGE_PROJECT_ASSETS
+
+
+
+
+ customer + copilot + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
+
+
+
+
+
+ Edit project status +
+
EDIT_PROJECT_STATUS
+
+
+
+
+ copilot + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
+
+
+
+
+
+ View project special links +
+
VIEW_PROJECT_SPECIAL_LINKS
+
Direct / Salesforce links
+
+
+
+ copilot + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ Connect Manager + administrator + Connect Admin +
+
+
+
+
+
+ Submit project for review +
+
SUBMIT_PROJECT_FOR_REVIEW
- administrator - Connect Admin - Connect Manager - Connect Copilot Manager - Connect Account Manager - Business Development Representative - Presales - Account Executive - Program Manager - Solution Architect - Project Manager +
+ customer +
+ +
+
+
+
+
+
+
+ Delete draft project +
+
DELETE_DRAFT_PROJECT
+
+
+
+
+ owner +
+ +
+
-

DEMO/TEST example permissions

+

+ Scope Change Requests +

- Topcoder role deny -
TEST_1
-
This permission is just to demonstrate possible format
+
+ Approve & Reject Scope Change Requests +
+
APPROVE_REJECT_SCOPE_REQUESTS
+
- Connect Copilot - Topcoder User - Account Executive +
+ customer +
+ +
+ administrator + Connect Admin +
- Topcoder role and Project role deny -
TEST_2
-
This permission is just to demonstrate possible format
+
+ Activate Scope Change Requests +
+
ACTIVATE_SCOPE_REQUESTS
+
- copilot - customer - manager - Connect Copilot - Topcoder User - Account Executive +
+ manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
- Any Project Member -
TEST_3
-
This permission is just to demonstrate possible format
+
+ Cancel Scope Change Requests (not own) +
+
CANCEL_SCOPE_REQUESTS_NOT_OWN
+
Everyone can cancel their own scope change requests.
- customer - copilot - manager - account_manager - account_executive - project_manager - program_manager - solution_architect +
+
+ +
+ administrator + Connect Admin +
+
+
+
+
+

+ Milestones +

+
+
+
+
+
+ Manage Milestones +
+
MANAGE_MILESTONE
+
Who can manage milestone and complete them.
+
+
+
+ copilot + manager + account_manager + account_executive + project_manager + program_manager + solution_architect +
+ +
+ administrator + Connect Admin +
- Any Logged-in User -
TEST_5
-
This permission is just to demonstrate possible format
+
+ Accept final delivery +
+
ACCEPT_MILESTONE_FINAL_DELIVERY
+
- Topcoder User - administrator - Connect Admin - Connect Manager - Connect Account Manager - Business Development Representative - Presales - Account Executive - Program Manager - Solution Architect - Project Manager - Connect Copilot +
+ customer +
+ +
+ administrator + Connect Admin +
- Owner Project role -
TEST_4
-
This permission is just to demonstrate possible format
+
+ Edit "Actual Start" and "Completion" dates. +
+
EDIT_MILESTONE_ACTUAL_START_COMPLETION_DATES
+
- owner +
+
+ +
+ administrator + Connect Admin +
+
+
+
+
+

+ Metadata +

+
+
+
+
+
+ Access Metadata +
+
ACCESS_METADATA
+
+
+
+
+
+ +
+ administrator + Connect Admin +
- \ No newline at end of file + diff --git a/scripts/permissions-doc/index.js b/scripts/permissions-doc/index.js index 51e43bd20..12b94d685 100644 --- a/scripts/permissions-doc/index.js +++ b/scripts/permissions-doc/index.js @@ -11,7 +11,7 @@ import _ from 'lodash' import fs from 'fs' import path from 'path' import handlebars from 'handlebars' -import PERMISSIONS from '../../src/config/permissions' +import { PERMISSIONS } from '../../src/config/permissions' import { PROJECT_ROLE_CUSTOMER, PROJECT_ROLE_COPILOT, @@ -42,6 +42,8 @@ import { const docTemplatePath = path.resolve(__dirname, './template.hbs') const outputDocPath = path.resolve(__dirname, '../../docs/permissions.html') +handlebars.registerHelper('istrue', value => value === true) + /** * All Project Roles */ @@ -121,8 +123,8 @@ function normalizePermission(permission) { if (!normalizedPermission.allowRule) { normalizedPermission = { - _meta: permission._meta, - allowRule: _.omit(permission, '_meta') + meta: permission.meta, + allowRule: _.omit(permission, 'meta') } } @@ -143,13 +145,17 @@ const renderDocument = handlebars.compile(templateStr) const permissionKeys = _.keys(PERMISSIONS) const permissions = permissionKeys.map((key) => ({ ...PERMISSIONS[key], - _meta: { - ...PERMISSIONS[key]._meta, + meta: { + ...PERMISSIONS[key].meta, key, } })) -const groupsObj = _.groupBy(permissions, '_meta.group') -const groups = _.toPairs(groupsObj).map(([title, permissions]) => ({ title, permissions })) +const groupsObj = _.groupBy(permissions, 'meta.group') +const groups = _.toPairs(groupsObj).map(([title, permissions]) => ({ + title, + permissions, + anchor: `section-${title.toLowerCase().replace(/\s+/g, '-')}`, +})) groups.forEach((group) => { group.permissions = group.permissions.map(normalizePermission) diff --git a/scripts/permissions-doc/template.hbs b/scripts/permissions-doc/template.hbs index facd79bc7..43a9f87c1 100644 --- a/scripts/permissions-doc/template.hbs +++ b/scripts/permissions-doc/template.hbs @@ -6,10 +6,53 @@ Permissions @@ -21,42 +64,59 @@

Legend:

{{#each groups}}
-

{{title}}

+

+ {{title}} +

{{#each permissions}}
- {{_meta.title}} -
{{_meta.key}}
-
{{_meta.description}}
+
+ {{meta.title}} +
+
{{meta.key}}
+
{{meta.description}}
- {{#each allowRule.projectRoles}} - {{this}} - {{/each}} - {{#each denyRule.projectRoles}} - {{this}} - {{/each}} - {{#each allowRule.topcoderRoles}} - {{this}} - {{/each}} - {{#each denyRule.topcoderRoles}} - {{this}} - {{/each}} +
+ {{#if (istrue allowRule.projectRoles)}} + Any Project Member + {{else}} + {{#each allowRule.projectRoles}} + {{this}} + {{/each}} + {{/if}} + {{#each denyRule.projectRoles}} + {{this}} + {{/each}} +
+ +
+ {{#if (istrue allowRule.topcoderRoles)}} + Any Logged-in User + {{else}} + {{#each allowRule.topcoderRoles}} + {{this}} + {{/each}} + {{/if}} + {{#each denyRule.topcoderRoles}} + {{this}} + {{/each}} +
{{/each}} {{/each}} - \ No newline at end of file + diff --git a/src/components/AssetsLibrary/FilesGridView.jsx b/src/components/AssetsLibrary/FilesGridView.jsx index 0f4cb2d1e..3a30fe9b5 100644 --- a/src/components/AssetsLibrary/FilesGridView.jsx +++ b/src/components/AssetsLibrary/FilesGridView.jsx @@ -25,7 +25,7 @@ import { PROJECT_FEED_TYPE_MESSAGES } from '../../config/constants' import { hasPermission } from '../../helpers/permissions' -import PERMISSIONS from '../../config/permissions' +import { PERMISSIONS } from '../../config/permissions' let selectedLink let clearing = false diff --git a/src/components/AssetsLibrary/LinksGridView.jsx b/src/components/AssetsLibrary/LinksGridView.jsx index 8688b344a..7689bab1d 100644 --- a/src/components/AssetsLibrary/LinksGridView.jsx +++ b/src/components/AssetsLibrary/LinksGridView.jsx @@ -22,7 +22,7 @@ import { } from '../../config/constants' import FilterColHeader from './FilterColHeader' import { hasPermission } from '../../helpers/permissions' -import PERMISSIONS from '../../config/permissions' +import { PERMISSIONS } from '../../config/permissions' let selectedLink let clearing = false diff --git a/src/components/Feed/Feed.jsx b/src/components/Feed/Feed.jsx index 9eec7834c..a2e647646 100644 --- a/src/components/Feed/Feed.jsx +++ b/src/components/Feed/Feed.jsx @@ -10,7 +10,7 @@ import RichTextArea from '../RichTextArea/RichTextArea' import NotificationsReader from '../../components/NotificationsReader' import IconButton from '../IconButton/IconButton' -import { EVENT_TYPE, PROJECT_FEED_TYPE_MESSAGES, PROJECT_ROLE_CUSTOMER } from '../../config/constants' +import { EVENT_TYPE, PROJECT_FEED_TYPE_MESSAGES } from '../../config/constants' import XMarkIcon from '../../assets/icons/x-mark.svg' import FullscreenIcon from '../../assets/icons/ui-fullscreen.svg' @@ -20,6 +20,8 @@ import CloseIcon from 'appirio-tech-react-components/components/Icons/CloseIcon' import MoreIcon from '../../assets/icons/more.svg' import './Feed.scss' +import { hasPermission } from '../../helpers/permissions' +import { PERMISSIONS } from '../../config/permissions' class Feed extends React.Component { constructor(props) { @@ -95,7 +97,7 @@ class Feed extends React.Component { } filterProjectMembers(projectMembers, isPrivate) { - return isPrivate ? _.pickBy(projectMembers, pm => pm.role !== PROJECT_ROLE_CUSTOMER) : projectMembers + return isPrivate ? _.filter(projectMembers, member => hasPermission(PERMISSIONS.ACCESS_PRIVATE_POST, { user: member })) : projectMembers } render() { diff --git a/src/components/IncomepleteUserProfileDialog/IncompleteUserProfileDialog.jsx b/src/components/IncomepleteUserProfileDialog/IncompleteUserProfileDialog.jsx index 625ff994b..52379dd1c 100644 --- a/src/components/IncomepleteUserProfileDialog/IncompleteUserProfileDialog.jsx +++ b/src/components/IncomepleteUserProfileDialog/IncompleteUserProfileDialog.jsx @@ -45,7 +45,6 @@ const IncompleteUserProfileDialog = ({ IncompleteUserProfileDialog.propTypes = { profileSettings: PT.object.isRequired, saveProfileSettings: PT.func.isRequired, - isTopcoderUser: PT.bool.isRequired, user: PT.object.isRequired, onCloseDialog: PT.func.isRequired, title: PT.string.isRequired, diff --git a/src/components/IncompleteUserProfile/IncompleteUserProfile.jsx b/src/components/IncompleteUserProfile/IncompleteUserProfile.jsx index bdf948102..92deb2708 100644 --- a/src/components/IncompleteUserProfile/IncompleteUserProfile.jsx +++ b/src/components/IncompleteUserProfile/IncompleteUserProfile.jsx @@ -3,19 +3,19 @@ */ import React from 'react' import PT from 'prop-types' -import { PROFILE_FIELDS_CONFIG } from '../../config/constants' import ProfileSettingsForm from '../../routes/settings/routes/profile/components/ProfileSettingsForm' -import { getDefaultTopcoderRole } from '../../helpers/permissions' +import { getDefaultTopcoderRole, hasPermission } from '../../helpers/permissions' import { timezones } from 'appirio-tech-react-components/constants/timezones' +import { getUserProfileFieldsConfig } from '../../helpers/tcHelpers' +import { PERMISSIONS } from '../../config/permissions' const IncompleteUserProfile = ({ profileSettings, saveProfileSettings, - isTopcoderUser, user, ...restProps }) => { - const fieldsConfig = isTopcoderUser ? PROFILE_FIELDS_CONFIG.TOPCODER : PROFILE_FIELDS_CONFIG.CUSTOMER + const fieldsConfig = getUserProfileFieldsConfig() // never show avatar delete fieldsConfig.avatar // config the form to only show required fields which doesn't have the value yet @@ -40,7 +40,7 @@ const IncompleteUserProfile = ({ console.log('Auto-detected timezone', prefilledProfileSettings.settings.timeZone) } - if (isTopcoderUser) { + if (!hasPermission(PERMISSIONS.VIEW_USER_PROFILE_AS_CUSTOMER)) { // We don't ask Topcoder User for "Company Name" and "Title" // but server requires them, so if they are not yet defined, we set them automatically if (!profileSettings.settings.companyName) { @@ -71,7 +71,6 @@ const IncompleteUserProfile = ({ IncompleteUserProfile.propTypes = { profileSettings: PT.object.isRequired, saveProfileSettings: PT.func.isRequired, - isTopcoderUser: PT.bool.isRequired, user: PT.object.isRequired, } diff --git a/src/components/Posts/PostsContainer.jsx b/src/components/Posts/PostsContainer.jsx index 0169dca2a..7e05e8d64 100644 --- a/src/components/Posts/PostsContainer.jsx +++ b/src/components/Posts/PostsContainer.jsx @@ -60,7 +60,7 @@ class PostsContainer extends React.Component { * which is accepted by Feed component */ prepareFeed() { - const { topic, error, allMembers, currentMemberRole, tag } = this.props + const { topic, error, allMembers, tag } = this.props const { showAll } = this.state if (!topic || !tag) { @@ -73,7 +73,7 @@ class PostsContainer extends React.Component { // Github issue##623, allow comments on all posts (including system posts) allowComments: true, user: isSystemUser(topic.userId) ? SYSTEM_USER : allMembers[topic.userId], - unread: !topic.read && !!currentMemberRole, + unread: !topic.read, totalComments: topic.totalPosts, comments: [], } @@ -88,7 +88,7 @@ class PostsContainer extends React.Component { isSavingComment: p.isSavingComment, isDeletingComment: p.isDeletingComment, error: p.error, - unread: !p.read && !!currentMemberRole, + unread: !p.read, date, createdAt: p.date, edited, diff --git a/src/components/ProjectInfo/ProjectInfo.jsx b/src/components/ProjectInfo/ProjectInfo.jsx index 8ee0eb1a7..27153f1ae 100644 --- a/src/components/ProjectInfo/ProjectInfo.jsx +++ b/src/components/ProjectInfo/ProjectInfo.jsx @@ -7,7 +7,7 @@ import DeleteProjectModal from './DeleteProjectModal' import ProjectCardBody from '../../projects/components/projectsCard/ProjectCardBody' import MobileExpandable from '../MobileExpandable/MobileExpandable' import MediaQuery from 'react-responsive' -import { SCREEN_BREAKPOINT_MD, PROJECT_ROLE_OWNER, PROJECT_ROLE_CUSTOMER } from '../../config/constants' +import { SCREEN_BREAKPOINT_MD } from '../../config/constants' import ReviewProjectButton from '../../projects/detail/components/ReviewProjectButton' import Tooltip from 'appirio-tech-react-components/components/Tooltip/Tooltip' import { TOOLTIP_DEFAULT_DELAY } from '../../config/constants' @@ -15,6 +15,8 @@ import { getProjectTemplateByKey } from '../../helpers/templates' import ProjectTypeIcon from '../../components/ProjectTypeIcon' import './ProjectInfo.scss' +import { hasPermission } from '../../helpers/permissions' +import { PERMISSIONS } from '../../config/permissions' class ProjectInfo extends Component { @@ -23,15 +25,14 @@ class ProjectInfo extends Component { } render() { - const { project, currentMemberRole, - onChangeStatus, isSuperUser, onSubmitForReview, isProjectProcessing, + const { project, + onChangeStatus, onSubmitForReview, isProjectProcessing, showDeleteConfirm, toggleProjectDelete, onConfirmDelete, projectTemplates } = this.props const code = _.get(project, 'details.utm.code', '') // prepare review button - const showReviewBtn = project.status === 'draft' && - _.indexOf([PROJECT_ROLE_OWNER, PROJECT_ROLE_CUSTOMER], currentMemberRole) > -1 + const showReviewBtn = project.status === 'draft' && hasPermission(PERMISSIONS.SUBMIT_PROJECT_FOR_REVIEW) const reviewButtonSection = (
@@ -102,7 +103,6 @@ class ProjectInfo extends Component { {matches => ( @@ -127,7 +126,6 @@ class ProjectInfo extends Component { ProjectInfo.propTypes = { project: PT.object.isRequired, - currentMemberRole: PT.string, productsTimelines: PT.object.isRequired, updateProject: PT.func, isProjectProcessing: PT.bool, diff --git a/src/components/TeamManagement/CopilotManagementDialog.js b/src/components/TeamManagement/CopilotManagementDialog.js index b76cb11a6..4f96f2120 100644 --- a/src/components/TeamManagement/CopilotManagementDialog.js +++ b/src/components/TeamManagement/CopilotManagementDialog.js @@ -5,7 +5,7 @@ import moment from 'moment' import Modal from 'react-modal' import XMarkIcon from '../../assets/icons/icon-x-mark.svg' import Avatar from 'appirio-tech-react-components/components/Avatar/Avatar' -import PERMISSIONS from '../../config/permissions' +import { PERMISSIONS } from '../../config/permissions' import { hasPermission } from '../../helpers/permissions' import {getAvatarResized, getFullNameWithFallback} from '../../helpers/tcHelpers' @@ -88,10 +88,10 @@ class ProjectManagementDialog extends React.Component { render() { const { - members, currentUser, isMember, removeMember, removeInvite, + members, currentUser, removeMember, removeInvite, onCancel, copilotTeamInvites = [], selectedMembers, processingInvites, } = this.props - const showRemove = currentUser.isAdmin || (!currentUser.isCopilot && isMember) + const canManageCopilots = hasPermission(PERMISSIONS.MANAGE_COPILOTS) const showSuggestions = hasPermission(PERMISSIONS.SEE_MEMBER_SUGGESTIONS) let i = 0 return ( @@ -111,7 +111,7 @@ class ProjectManagementDialog extends React.Component {
{(members.map((member) => { - if (!member.isCopilot) { + if (!hasPermission(PERMISSIONS.BE_LISTED_IN_COPILOT_TEAM, { user: member })) { return null } i++ @@ -138,7 +138,7 @@ class ProjectManagementDialog extends React.Component {
- {showRemove &&
+ {canManageCopilots &&
{(currentUser.userId === member.userId) ? 'Leave' : 'Remove'}
}
@@ -169,7 +169,7 @@ class ProjectManagementDialog extends React.Component { { (!hasUserId) && {invite.email}} - {showRemove &&
+ {canManageCopilots &&
Remove Invited {moment(invite.createdAt).format('MMM D, YY')} @@ -178,33 +178,37 @@ class ProjectManagementDialog extends React.Component {
) }))} + {i === 0 && !canManageCopilots &&
}
-
-
invite more copilots
- - {this.state.showAlreadyMemberError &&
- Project Member(s) can't be invited again. Please remove them from list. -
} - { this.state.errorMessage &&
- {this.state.errorMessage} -
} - -
+ {canManageCopilots && ( +
+
invite more copilots
+ + {this.state.showAlreadyMemberError &&
+ Project Member(s) can't be invited again. Please remove them from list. +
} + { this.state.errorMessage &&
+ {this.state.errorMessage} +
} + +
+ )} + {!canManageCopilots &&
}
diff --git a/src/components/TeamManagement/ProjectManagementDialog.js b/src/components/TeamManagement/ProjectManagementDialog.js index 537c1038f..96d23a7e7 100644 --- a/src/components/TeamManagement/ProjectManagementDialog.js +++ b/src/components/TeamManagement/ProjectManagementDialog.js @@ -5,7 +5,7 @@ import moment from 'moment' import Modal from 'react-modal' import XMarkIcon from '../../assets/icons/icon-x-mark.svg' import Avatar from 'appirio-tech-react-components/components/Avatar/Avatar' -import PERMISSIONS from '../../config/permissions' +import { PERMISSIONS } from '../../config/permissions' import { hasPermission } from '../../helpers/permissions' import {getAvatarResized, getFullNameWithFallback} from '../../helpers/tcHelpers' @@ -88,10 +88,10 @@ class ProjectManagementDialog extends React.Component { render() { const { - members, currentUser, isMember, removeMember, removeInvite, + members, currentUser, removeMember, removeInvite, onCancel, projectTeamInvites = [], selectedMembers, processingInvites, } = this.props - const showRemove = currentUser.isAdmin || (!currentUser.isCopilot && isMember) + const canManageCustomerTeam = hasPermission(PERMISSIONS.MANAGE_CUSTOMER_TEAM) const showSuggestions = hasPermission(PERMISSIONS.SEE_MEMBER_SUGGESTIONS) let i = 0 return ( @@ -111,7 +111,7 @@ class ProjectManagementDialog extends React.Component {
{(members.map((member) => { - if (!member.isCustomer) { + if (!hasPermission(PERMISSIONS.BE_LISTED_IN_CUSTOMER_TEAM, { user: member})) { return null } i++ @@ -138,7 +138,7 @@ class ProjectManagementDialog extends React.Component {
- {showRemove &&
+ {canManageCustomerTeam &&
{(currentUser.userId === member.userId) ? 'Leave' : 'Remove'}
}
@@ -169,7 +169,7 @@ class ProjectManagementDialog extends React.Component { { (!hasUserId) && {invite.email}} - {showRemove &&
+ {canManageCustomerTeam &&
Remove Invited {moment(invite.createdAt).format('MMM D, YY')} @@ -178,35 +178,38 @@ class ProjectManagementDialog extends React.Component {
) }))} + {i === 0 && !canManageCustomerTeam &&
}
-
-
invite more people
- - {this.state.showAlreadyMemberError &&
- Project Member(s) can't be invited again. Please remove them from list. -
} - { this.state.errorMessage &&
- {this.state.errorMessage} -
} - -
+ {canManageCustomerTeam && ( +
+
invite more people
+ + {this.state.showAlreadyMemberError &&
+ Project Member(s) can't be invited again. Please remove them from list. +
} + { this.state.errorMessage &&
+ {this.state.errorMessage} +
} + +
+ )} + {!canManageCustomerTeam &&
}
- ) } diff --git a/src/components/TeamManagement/TeamManagement.jsx b/src/components/TeamManagement/TeamManagement.jsx index 603f08683..9b94a0a6b 100644 --- a/src/components/TeamManagement/TeamManagement.jsx +++ b/src/components/TeamManagement/TeamManagement.jsx @@ -8,7 +8,7 @@ import TopcoderDialog from './TopcoderManagementDialog' import MemberItem from './MemberItem' import AddIcon from '../../assets/icons/icon-ui-bold-add.svg' import Dialog from './Dialog' -import PERMISSIONS from '../../config/permissions' +import { PERMISSIONS } from '../../config/permissions' import {hasPermission} from '../../helpers/permissions' import { getFullNameWithFallback } from '../../helpers/tcHelpers' @@ -17,10 +17,7 @@ const userShape = PropTypes.shape({ handle: PropTypes.string.isRequired, photoURL: PropTypes.string, role: PropTypes.string, - isPrimary: PropTypes.bool, - isManager: PropTypes.bool, - isCopilot: PropTypes.bool, - isCustomer: PropTypes.bool + isPrimary: PropTypes.bool }) const REMOVE_INVITATION_TITLE = 'You\'re about to remove an invitation' @@ -111,13 +108,11 @@ class TeamManagement extends React.Component { const currentMember = members.filter((member) => member.userId === currentUser.userId)[0] const modalActive = isAddingTeamMember || deletingMember || isShowJoin || showNewMemberConfirmation || deletingInvite - const customerTeamManageAction = (currentUser.isAdmin || currentUser.isManager) && !currentMember + const customerTeamManageAction = hasPermission(PERMISSIONS.MANAGE_CUSTOMER_TEAM) const topcoderTeamManageAction = hasPermission(PERMISSIONS.MANAGE_TOPCODER_TEAM) const copilotTeamManageAction = hasPermission(PERMISSIONS.MANAGE_COPILOTS) const canRequestCopilot = hasPermission(PERMISSIONS.REQUEST_COPILOTS) - const canJoinAsCopilot = !currentMember && currentUser.isCopilot const canJoinTopcoderTeam = !currentMember && hasPermission(PERMISSIONS.JOIN_TOPCODER_TEAM) - const canShowInvite = currentMember && (currentMember.isCustomer || currentMember.isCopilot || currentMember.isManager) const sortedMembers = members let projectTeamInviteCount = 0 @@ -129,15 +124,15 @@ class TeamManagement extends React.Component {
Team - {(customerTeamManageAction) && + {!customerTeamManageAction && onShowProjectDialog(true)}> - {currentUser.isAdmin ? 'Manage' : 'View'} + View }
{sortedMembers.map((member, i) => { - if (!member.isCustomer) { + if (!hasPermission(PERMISSIONS.BE_LISTED_IN_CUSTOMER_TEAM, { user: member })) { return } projectTeamInviteCount++ @@ -179,7 +174,7 @@ class TeamManagement extends React.Component {
} - { (canShowInvite) && + {customerTeamManageAction &&
onShowProjectDialog(true)}> @@ -204,7 +199,7 @@ class TeamManagement extends React.Component {
{sortedMembers.map((member, i) => { - if (!member.isCopilot) { + if (!hasPermission(PERMISSIONS.BE_LISTED_IN_COPILOT_TEAM, { user: member })) { return } @@ -263,7 +258,7 @@ class TeamManagement extends React.Component {
{sortedMembers.map((member, i) => { - if (member.isCustomer || member.isCopilot) { + if (!hasPermission(PERMISSIONS.BE_LISTED_IN_TOPCODER_TEAM, { user: member })) { return } @@ -299,12 +294,12 @@ class TeamManagement extends React.Component {
} - { (canJoinAsCopilot || canJoinTopcoderTeam) && + {canJoinTopcoderTeam &&
onJoin(true)}>
- {canJoinAsCopilot ? 'Join project as copilot' : 'Join project'} + Join project
@@ -313,14 +308,12 @@ class TeamManagement extends React.Component {
{isShowJoin && ((() => { const onClickCancel = () => onJoin(false) - let role = 'Manager' - if (currentUser.isCopilot) role = 'Copilot' return ( {(members.map((member) => { - if (member.isCustomer || member.isCopilot) { + if (!hasPermission(PERMISSIONS.BE_LISTED_IN_TOPCODER_TEAM, { user: member })) { return null } i++ @@ -146,10 +146,10 @@ class TopcoderManagementDialog extends React.Component { - {showRemove &&
+ {canManageTopcoderTeam &&
{(currentUser.userId === member.userId) ? 'Leave' : 'Remove'}
} - {!showRemove && (currentUser.userId === member.userId) && + {!canManageTopcoderTeam && (currentUser.userId === member.userId) &&
Leave
@@ -213,7 +213,7 @@ class TopcoderManagementDialog extends React.Component {
} { - invite.status===PROJECT_MEMBER_INVITE_STATUS_REQUESTED && !showApproveDecline && showRemove && + invite.status===PROJECT_MEMBER_INVITE_STATUS_REQUESTED && !showApproveDecline && canManageTopcoderTeam &&
Requested {moment(invite.createdAt).format('MMM D, YY')} @@ -221,7 +221,7 @@ class TopcoderManagementDialog extends React.Component {
} { - invite.status===PROJECT_MEMBER_INVITE_STATUS_PENDING && showRemove && + invite.status===PROJECT_MEMBER_INVITE_STATUS_PENDING && canManageTopcoderTeam &&
Remove @@ -230,7 +230,7 @@ class TopcoderManagementDialog extends React.Component {
} { - invite.status===PROJECT_MEMBER_INVITE_STATUS_PENDING && !showRemove && + invite.status===PROJECT_MEMBER_INVITE_STATUS_PENDING && !canManageTopcoderTeam &&
Invited {moment(invite.createdAt).format('MMM D, YY')} @@ -241,16 +241,17 @@ class TopcoderManagementDialog extends React.Component {
) }))} + {i === 0 && !canManageTopcoderTeam &&
}
- {(showRemove || showApproveDecline) &&
+ {canManageTopcoderTeam &&
invite more people
{ this.state.showAlreadyMemberError &&
@@ -269,7 +270,7 @@ class TopcoderManagementDialog extends React.Component {
} - {!showRemove &&
} + {!canManageTopcoderTeam &&
}
) diff --git a/src/components/TopBar/ProjectToolBar.js b/src/components/TopBar/ProjectToolBar.js index ceebeb995..f80934a9c 100644 --- a/src/components/TopBar/ProjectToolBar.js +++ b/src/components/TopBar/ProjectToolBar.js @@ -85,7 +85,6 @@ class ProjectToolBar extends React.Component { ProjectToolBar.propTypes = { isProjectLoading: PT.bool, project: PT.object, - isPowerUser: PT.bool, /** * Function which render the logo section in the top bar */ diff --git a/src/components/TopBar/ProjectsToolBar.js b/src/components/TopBar/ProjectsToolBar.js index 909e578b2..2ebf530d0 100644 --- a/src/components/TopBar/ProjectsToolBar.js +++ b/src/components/TopBar/ProjectsToolBar.js @@ -14,6 +14,8 @@ import MobileMenuToggle from '../MobileMenu/MobileMenuToggle' import { projectSuggestions, loadProjects, setInfiniteAutoload } from '../../projects/actions/loadProjects' import { loadProjectsMetadata } from '../../actions/templates' import { getNewProjectLink } from '../../helpers/projectHelper' +import { hasPermission } from '../../helpers/permissions' +import { PERMISSIONS } from '../../config/permissions' class ProjectsToolBar extends Component { @@ -144,7 +146,7 @@ class ProjectsToolBar extends Component { } render() { - const { renderLogoSection, userMenu, userRoles, isPowerUser, user, mobileMenu, location, orgConfig } = this.props + const { renderLogoSection, userMenu, userRoles, user, mobileMenu, location, orgConfig } = this.props const { isMobileMenuOpen, isMobileSearchVisible } = this.state const isLoggedIn = !!(userRoles && userRoles.length) @@ -158,9 +160,9 @@ class ProjectsToolBar extends Component { />
{ renderLogoSection() } - { isLoggedIn && !isPowerUser &&
MY PROJECTS
} + { isLoggedIn && !hasPermission(PERMISSIONS.SEARCH_PROJECTS) &&
MY PROJECTS
} { - isLoggedIn && !!isPowerUser && + isLoggedIn && hasPermission(PERMISSIONS.SEARCH_PROJECTS) &&
) } - const { user, toolbar, userRoles, isPowerUser } = this.props + const { user, toolbar, userRoles } = this.props const userHandle = _.get(user, 'handle') const bigPhotoURL = _.get(user, 'photoURL') const userImage = getAvatarResized(bigPhotoURL, 80) @@ -99,7 +89,7 @@ class TopBarContainer extends React.Component { { style: 'big', items: [ - { label: 'All projects', link: isPowerUser ? '/projects?sort=updatedAt%20desc' : '/projects' }, + { label: 'All projects', link: '/projects?sort=updatedAt%20desc' }, { label: 'My profile', link: '/settings/profile' }, { label: 'Account and security', link: '/settings/account' }, { label: 'Notification settings', link: '/settings/notifications' }, @@ -163,31 +153,10 @@ class TopBarContainer extends React.Component { } } -const mapStateToProps = ({ loadUser }) => { - let isPowerUser = false - const roles = [ - ROLE_CONNECT_COPILOT, - ROLE_CONNECT_MANAGER, - ROLE_CONNECT_ACCOUNT_MANAGER, - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, - - ROLE_BUSINESS_DEVELOPMENT_REPRESENTATIVE, - ROLE_PRESALES, - ROLE_ACCOUNT_EXECUTIVE, - ROLE_PROJECT_MANAGER, - ROLE_PROGRAM_MANAGER, - ROLE_SOLUTION_ARCHITECT, - ] - if (loadUser.user) { - isPowerUser = loadUser.user.roles.some((role) => roles.indexOf(role) !== -1) - } - return { - userRoles : _.get(loadUser, 'user.roles', []), - user : loadUser.user, - isPowerUser - } -} +const mapStateToProps = ({ loadUser }) => ({ + userRoles : _.get(loadUser, 'user.roles', []), + user : loadUser.user, +}) const actionsToBind = { } diff --git a/src/components/TopicDrawer/TopicDrawer.jsx b/src/components/TopicDrawer/TopicDrawer.jsx index 8977f72c5..54a31a032 100644 --- a/src/components/TopicDrawer/TopicDrawer.jsx +++ b/src/components/TopicDrawer/TopicDrawer.jsx @@ -12,8 +12,6 @@ import './TopicDrawer.scss' const TopicDrawer = ({ open, onClose, - currentMemberRole, - isSuperUser, project, topic }) => { @@ -32,9 +30,7 @@ const TopicDrawer = ({ > {open && ( } permissionRule.projectRoles the list of project roles of the user * @param {Array} permissionRule.topcoderRoles the list of Topcoder roles of the user - * @param {Object} [project] project object - required to check `topcoderRoles` - * @param {Array} project.members list of project members - required to check `topcoderRoles` + * @param {Object} [entities] `project` and `user` which has to be used to check permission + * @param {Object} [entities.project] project + * @param {Array} entities.project.members list of project members + * @param {Object} [entities.user] user + * @param {String} entities.user.role user Project Role * * @returns {Boolean} true, if has permission */ -export const hasPermission = (permission, project) => { - const user = _.get(store.getState(), 'loadUser.user', {}) - const projectData = project || _.get(store.getState(), 'projectState.project') +export const hasPermission = (permission, entities = {}) => { + const user = entities.user || _.get(store.getState(), 'loadUser.user', {}) + const project = entities.project || _.get(store.getState(), 'projectState.project') const allowRule = permission.allowRule ? permission.allowRule : permission const denyRule = permission.denyRule ? permission.denyRule : null - const allow = matchPermissionRule(allowRule, user, projectData) - const deny = matchPermissionRule(denyRule, user, projectData) + const allow = matchPermissionRule(allowRule, user, project) + const deny = matchPermissionRule(denyRule, user, project) // uncomment for debugging - // console.warn('hasPermission', permission, projectData, allow && !deny) + // if (permission === PERMISSIONS.VIEW_DRAFT_PHASES) { + // console.warn('hasPermission', permission, project, user, allow && !deny) + // } return allow && !deny } @@ -165,7 +88,7 @@ export const hasPermission = (permission, project) => { * @param {Array|Array|Boolean} permissionRule.projectRoles the list of project roles of the user * @param {Array|Boolean} permissionRule.topcoderRoles the list of Topcoder roles of the user * @param {Object} user user for whom we check permissions - * @param {Object} user.roles list of user roles + * @param {String} user.role user Project Role * @param {Object} [project] project object - required to check `topcoderRoles` * @param {Array} project.members list of project members - required to check `topcoderRoles` * @@ -185,15 +108,18 @@ const matchPermissionRule = (permissionRule, user, project) => { && project && project.members ) { + if (_.some(permissionRule.projectRoles, (rule) => _.isArray(rule))) { + throw new Error('Role cannot be an array. Make sure, that "projectRoles" doesn\'t have nested arrays: ' + JSON.stringify(permissionRule.projectRoles)) + } const userId = !_.isNumber(user.userId) ? parseInt(user.userId, 10) : user.userId const member = _.find(project.members, { userId }) - // as we support `projectRoles` as strings and as objects like: - // { role: "...", isPrimary: true } we have normalize them to a common shape - const normalizedProjectRoles = permissionRule.projectRoles.map((rule) => ( - _.isString(rule) ? { role: rule } : rule - )) if (permissionRule.projectRoles.length > 0) { + // as we support `projectRoles` as strings and as objects like: + // { role: "...", isPrimary: true } we have normalize them to a common shape + const normalizedProjectRoles = permissionRule.projectRoles.map((rule) => ( + _.isString(rule) ? { role: rule } : rule + )) hasProjectRole = member && _.some(normalizedProjectRoles, (rule) => ( // checks that common properties are equal _.isMatch(member, rule) @@ -207,12 +133,15 @@ const matchPermissionRule = (permissionRule, user, project) => { // check Topcoder Roles if (permissionRule.topcoderRoles) { + if (_.some(permissionRule.topcoderRoles, (rule) => _.isArray(rule))) { + throw new Error('Role cannot be an array. Make sure, that "topcoderRoles" doesn\'t have nested arrays: ' + JSON.stringify(permissionRule.topcoderRoles)) + } if (permissionRule.topcoderRoles.length > 0) { hasTopcoderRole = _.intersection( _.get(user, 'roles', []).map(role => role.toLowerCase()), permissionRule.topcoderRoles.map(role => role.toLowerCase()) ).length > 0 - } else if (permissionRule.topcoderRoles === 'true') { + } else if (permissionRule.topcoderRoles === true) { // `topcoderRoles === true` means that we check if user is has any Topcoder role // basically this equals to logged-in user, as all the Topcoder users // have at least one role `Topcoder User` diff --git a/src/helpers/projectHelper.js b/src/helpers/projectHelper.js index cfb84b381..3b9448535 100644 --- a/src/helpers/projectHelper.js +++ b/src/helpers/projectHelper.js @@ -3,8 +3,6 @@ import moment from 'moment' import { findProduct } from '../config/projectWizard' import { - PROJECT_ROLE_CUSTOMER, - PROJECT_ROLE_OWNER, PHASE_STATUS_ACTIVE, PHASE_STATUS_COMPLETED, PHASE_STATUS_REVIEWED, @@ -23,19 +21,6 @@ import InvisibleIcon from '../assets/icons/invisible.svg' import { formatNumberWithCommas } from './format' -export const getProjectRoleForCurrentUser = ({currentUserId, project}) => { - let role = null - if (project) { - const member = _.find(project.members, m => m.userId === currentUserId) - if (member) { - role = member.role - if (role === PROJECT_ROLE_CUSTOMER && member.isPrimary) - role = PROJECT_ROLE_OWNER - } - } - return role -} - /** * Format ProjectProgress props * @@ -288,7 +273,7 @@ export function getProjectNavLinks(project, projectId, renderFAQs) { messagesTab, { label: 'Specification', to: `/projects/${projectId}/specification`, Icon: ScopeIcon, iconClassName: 'fill' }, ] - + if (renderFAQs) { const faqTab = { label: 'FAQ', to: `/projects/${projectId}/faqs`, Icon: FAQIcon, iconClassName: 'fill' } navLinks.push(faqTab) diff --git a/src/helpers/tcHelpers.js b/src/helpers/tcHelpers.js index d86af5825..c91fef573 100644 --- a/src/helpers/tcHelpers.js +++ b/src/helpers/tcHelpers.js @@ -6,11 +6,10 @@ import { CODER_BOT_USERID, TC_SYSTEM_USERID, TC_CDN_URL, - NON_CUSTOMER_ROLES, PROFILE_FIELDS_CONFIG, } from '../config/constants' import { hasPermission } from './permissions' -import PERMISSIONS from '../config/permissions' +import { PERMISSIONS } from '../config/permissions' /** * Check if a user is a special system user @@ -59,8 +58,7 @@ export const getFullNameWithFallback = (user) => { * @returns {Boolean} complete or no */ export const isUserProfileComplete = (user, profileSettings) => { - const isTopcoderUser = _.intersection(user.roles, NON_CUSTOMER_ROLES).length > 0 - const fieldsConfig = isTopcoderUser ? PROFILE_FIELDS_CONFIG.TOPCODER : PROFILE_FIELDS_CONFIG.CUSTOMER + const fieldsConfig = getUserProfileFieldsConfig() // check if any required field doesn't have a value let isMissingUserInfo = false diff --git a/src/projects/actions/loadProjects.js b/src/projects/actions/loadProjects.js index 2b9cc6d59..15078ec02 100644 --- a/src/projects/actions/loadProjects.js +++ b/src/projects/actions/loadProjects.js @@ -6,10 +6,11 @@ import { SET_PROJECTS_INFINITE_AUTOLOAD, SET_PROJECTS_LIST_VIEW, PROJECT_STATUS_ACTIVE, - ROLE_TOPCODER_USER } from '../../config/constants' import { getProjects } from '../../api/projects' import { loadMembers } from '../../actions/members' +import { hasPermission } from '../../helpers/permissions' +import { PERMISSIONS } from '../../config/permissions' // ignore action /*eslint-disable no-unused-vars */ @@ -26,8 +27,7 @@ const getProjectsWithMembers = (dispatch, getState, criteria, pageNum) => { type: GET_PROJECTS, payload: getProjects(criteria, pageNum) .then((originalData) => { - const retryForCustomer = criteria.status === PROJECT_STATUS_ACTIVE && state.loadUser.user.roles && state.loadUser.user.roles.length === 1 - && state.loadUser.user.roles[0] === ROLE_TOPCODER_USER + const retryForCustomer = criteria.status === PROJECT_STATUS_ACTIVE && hasPermission(PERMISSIONS.RETRY_PROJECTS_LOADING) if(originalData.totalCount === 0 && retryForCustomer) { //retrying for customer if active projects are 0 but there are some projects with other status //This is to bypass the walkthrough page which we ideally show for customer with zero projects diff --git a/src/projects/components/projectsCard/ProjectCard.jsx b/src/projects/components/projectsCard/ProjectCard.jsx index 4d3ad2866..1265508ec 100644 --- a/src/projects/components/projectsCard/ProjectCard.jsx +++ b/src/projects/components/projectsCard/ProjectCard.jsx @@ -2,7 +2,6 @@ import React from 'react' import PT from 'prop-types' import _ from 'lodash' import { withRouter } from 'react-router-dom' -import { getProjectRoleForCurrentUser } from '../../../helpers/projectHelper' import ProjectCardHeader from './ProjectCardHeader' import ProjectCardBody from './ProjectCardBody' import ProjectManagerAvatars from '../../list/components/Projects/ProjectManagerAvatars' @@ -12,7 +11,6 @@ import './ProjectCard.scss' function ProjectCard({ project, disabled, currentUser, history, onChangeStatus, projectTemplates, unreadMentionsCount, callInviteRequest, isAcceptingInvite }) { const className = `ProjectCard ${ disabled ? 'disabled' : 'enabled'}` if (!project) return null - const currentMemberRole = getProjectRoleForCurrentUser({ project, currentUserId: currentUser.userId}) // check whether is the project's member const isMember = _.some(project.members, m => (m.userId === currentUser.userId && m.deletedAt === null)) // check whether has pending invition @@ -34,7 +32,6 @@ function ProjectCard({ project, disabled, currentUser, history, onChangeStatus,
-1))) + project.status !== PROJECT_STATUS_COMPLETED && hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) ) const goToProjectDetails = (evt, showLinkURL, projectDetailsURL) => { @@ -59,7 +51,6 @@ function ProjectCardBody({ project, currentMemberRole, descLinesCount = 8, status={project.status} showText withoutLabel - currentMemberRole={currentMemberRole} canEdit={canEdit} unifiedHeader={false} onChangeStatus={onChangeStatus} @@ -80,7 +71,6 @@ ProjectCardBody.defaultTypes = { ProjectCardBody.propTypes = { project: PT.object.isRequired, - currentMemberRole: PT.string, showLink: PT.bool, showLinkURL: PT.string, canEditStatus: PT.bool, diff --git a/src/projects/create/components/FillProjectDetails.js b/src/projects/create/components/FillProjectDetails.js index b0f8f238c..4b567a943 100644 --- a/src/projects/create/components/FillProjectDetails.js +++ b/src/projects/create/components/FillProjectDetails.js @@ -16,15 +16,6 @@ import { uploadProfilePhoto, resetProfileSetting, } from '../../../routes/settings/actions/index' -import { - ROLE_CONNECT_COPILOT, - ROLE_CONNECT_MANAGER, - ROLE_CONNECT_ACCOUNT_MANAGER, - ROLE_CONNECT_COPILOT_MANAGER, - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, - NON_CUSTOMER_ROLES, -} from '../../../config/constants' import { isUserProfileComplete } from '../../../helpers/tcHelpers' class FillProjectDetails extends Component { @@ -270,20 +261,6 @@ FillProjectDetails.propTypes = { } const mapStateToProps = ({ settings, loadUser }) => { - const powerUserRoles = [ - ROLE_CONNECT_COPILOT, - ROLE_CONNECT_MANAGER, - ROLE_CONNECT_ACCOUNT_MANAGER, - ROLE_CONNECT_ADMIN, - ROLE_ADMINISTRATOR, - ROLE_CONNECT_COPILOT_MANAGER, - ] - const managerRoles = [ - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, - ROLE_CONNECT_MANAGER, - ] - const isTopcoderUser = _.intersection(loadUser.user.roles, NON_CUSTOMER_ROLES).length > 0 const profileSettings = formatProfileSettings(settings.profile.traits) return { @@ -293,14 +270,6 @@ const mapStateToProps = ({ settings, loadUser }) => { }, isLoadedProfileSetting: !_.isEmpty(profileSettings), user: loadUser.user, - isCustomer: - _.intersection(loadUser.user.roles, powerUserRoles).length === 0, - isManager: _.intersection(loadUser.user.roles, managerRoles).length > 0, - isCopilot: _.some( - loadUser.user.roles, - (role) => role === ROLE_CONNECT_COPILOT - ), - isTopcoderUser, isMissingUserInfo: !isUserProfileComplete(loadUser.user, profileSettings), } } diff --git a/src/projects/create/components/UpdateUserInfo.js b/src/projects/create/components/UpdateUserInfo.js index 96e5fc010..d77ad9b8d 100644 --- a/src/projects/create/components/UpdateUserInfo.js +++ b/src/projects/create/components/UpdateUserInfo.js @@ -5,6 +5,8 @@ import TailLeft from '../../../assets/icons/arrows-16px-1_tail-left.svg' import { DOMAIN } from '../../../config/constants' import './UpdateUserInfo.scss' import IncompleteUserProfile from '../../../components/IncompleteUserProfile/IncompleteUserProfile' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' class UpdateUserInfo extends Component { constructor(props) { @@ -22,7 +24,6 @@ class UpdateUserInfo extends Component { const { profileSettings, saveProfileSettings, - isTopcoderUser, closeUserSettings, user, } = this.props @@ -48,14 +49,13 @@ class UpdateUserInfo extends Component { - {!isTopcoderUser && ( + {hasPermission(PERMISSIONS.VIEW_USER_PROFILE_AS_CUSTOMER) && (

Were you looking to join Topcoder’s freelancer community and participate in work? Click here @@ -74,7 +74,6 @@ UpdateUserInfo.defaultProps = { UpdateUserInfo.propTypes = { profileSettings: PT.object.isRequired, isMissingUserInfo: PT.bool.isRequired, - isTopcoderUser: PT.bool.isRequired, closeUserSettings: PT.func, } diff --git a/src/projects/detail/Messages.jsx b/src/projects/detail/Messages.jsx index 3994cf853..4842434e9 100644 --- a/src/projects/detail/Messages.jsx +++ b/src/projects/detail/Messages.jsx @@ -4,11 +4,10 @@ import MessagesContainer from './containers/MessagesContainer' require('./Messages.scss') -const Messages = ({ location, project, currentMemberRole, route, params }) => ( +const Messages = ({ location, project, route, params }) => ( diff --git a/src/projects/detail/ProjectDetail.jsx b/src/projects/detail/ProjectDetail.jsx index b73b2c557..3c387045d 100644 --- a/src/projects/detail/ProjectDetail.jsx +++ b/src/projects/detail/ProjectDetail.jsx @@ -13,9 +13,8 @@ import { loadProjects } from '../actions/loadProjects' import { getEmptyProjectObject } from '../reducers/project' import { - LOAD_PROJECT_FAILURE, PROJECT_ROLE_CUSTOMER, PROJECT_ROLE_OWNER, - ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN, ROLE_CONNECT_COPILOT, ROLE_CONNECT_MANAGER, - PROJECT_MEMBER_INVITE_STATUS_ACCEPTED, PROJECT_MEMBER_INVITE_STATUS_REFUSED, ACCEPT_OR_REFUSE_INVITE_FAILURE, NON_CUSTOMER_ROLES + LOAD_PROJECT_FAILURE, + PROJECT_MEMBER_INVITE_STATUS_ACCEPTED, PROJECT_MEMBER_INVITE_STATUS_REFUSED, ACCEPT_OR_REFUSE_INVITE_FAILURE } from '../../config/constants' import spinnerWhileLoading from '../../components/LoadingSpinner' import CoderBot from '../../components/CoderBot/CoderBot' @@ -77,11 +76,6 @@ const spinner = spinnerWhileLoading(props => ) ) const ProjectDetailView = (props) => { - let currentMemberRole = props.currentMemberRole - if (!currentMemberRole && props.currentUserRoles && props.currentUserRoles.length > 0) { - currentMemberRole = props.currentUserRoles[0] - } - const template = _.get(props.projectTemplate, 'scope', {}) let estimationQuestion = null const { estimateBlocks } = getProductEstimate({scope: template}, props.project) @@ -108,10 +102,6 @@ const ProjectDetailView = (props) => { const componentProps = { project: props.project, projectNonDirty: props.projectNonDirty, - currentMemberRole: currentMemberRole || '', - isSuperUser: props.isSuperUser, - isManageUser: props.isManageUser, - isCustomerUser: props.isCustomerUser, isProcessing: props.isProcessing, allProductTemplates: props.allProductTemplates, productsTimelines: props.productsTimelines, @@ -215,19 +205,6 @@ class ProjectDetail extends Component { } } - getProjectRoleForCurrentUser({currentUserId, project}) { - let role = null - if (project) { - const member = _.find(project.members, m => m.userId === currentUserId) - if (member) { - role = member.role - if (role === PROJECT_ROLE_CUSTOMER && member.isPrimary) - role = PROJECT_ROLE_OWNER - } - } - return role - } - onUserInviteAction(isUserAcceptedInvitation) { if (this.state.isCallingInviteAction) { return @@ -275,14 +252,8 @@ class ProjectDetail extends Component { } render() { - const { inviteError, project, profileSettings, currentUser, saveProfileSettings, isTopcoderUser } = this.props + const { inviteError, project, profileSettings, currentUser, saveProfileSettings } = this.props const { isCallingInviteAction, isUserAcceptedInvitation, shouldForceCallAcceptRefuseRequest } = this.state - const currentMemberRole = this.getProjectRoleForCurrentUser(this.props) - const adminRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - const isSuperUser = this.props.currentUserRoles.some((role) => adminRoles.indexOf(role) !== -1) - const powerRoles = [ROLE_CONNECT_COPILOT, ROLE_CONNECT_MANAGER] - const isManageUser = this.props.currentUserRoles.some((role) => powerRoles.indexOf(role) !== -1) - const isCustomerUser = !(isManageUser || isSuperUser) const showUserInvited = this.props.showUserInvited return ( @@ -301,7 +272,6 @@ class ProjectDetail extends Component {

{this.state.showIncompleteProfilePopup && (
) @@ -325,7 +291,6 @@ class ProjectDetail extends Component { const mapStateToProps = ({projectState, projectDashboard, loadUser, productsTimelines, templates, settings}) => { const templateId = (projectState.project || {}).templateId const { projectTemplates, productTemplates } = templates - const isTopcoderUser = _.intersection(loadUser.user.roles, NON_CUSTOMER_ROLES).length > 0 return { currentUser: loadUser.user, currentUserId: parseInt(loadUser.user.id), @@ -355,7 +320,6 @@ const mapStateToProps = ({projectState, projectDashboard, loadUser, productsTime ...settings.profile, settings: formatProfileSettings(settings.profile.traits) }, - isTopcoderUser, } } diff --git a/src/projects/detail/components/FileListContainer.jsx b/src/projects/detail/components/FileListContainer.jsx index 4dbe7efe6..533ac2b07 100644 --- a/src/projects/detail/components/FileListContainer.jsx +++ b/src/projects/detail/components/FileListContainer.jsx @@ -4,7 +4,6 @@ import { connect } from 'react-redux' import _ from 'lodash' import FileList from '../../../components/FileList/FileList' import AddFiles from '../../../components/FileList/AddFiles' -import { getProjectRoleForCurrentUser } from '../../../helpers/projectHelper' import { uploadProjectAttachments, discardAttachments, changeAttachmentPermission } from '../../actions/projectAttachment' import AddFilePermission from '../../../components/FileList/AddFilePermissions' import { ATTACHMENT_TYPE_FILE } from '../../../config/constants' @@ -52,12 +51,6 @@ class FileListContainer extends Component { }) } - canManageFiles() { - const { currentUserId, project } = this.props - const role = getProjectRoleForCurrentUser({ currentUserId, project }) - return !!role - } - render() { const { files, diff --git a/src/projects/detail/components/PhaseCard/EditStageForm.jsx b/src/projects/detail/components/PhaseCard/EditStageForm.jsx index 991f0b655..523dd90b2 100644 --- a/src/projects/detail/components/PhaseCard/EditStageForm.jsx +++ b/src/projects/detail/components/PhaseCard/EditStageForm.jsx @@ -20,10 +20,8 @@ import Tooltip from 'appirio-tech-react-components/components/Tooltip/Tooltip' import { TOOLTIP_DEFAULT_DELAY } from '../../../../config/constants' import { getPhaseActualData } from '../../../../helpers/projectHelper' import DeletePhase from './DeletePhase' -import { - ROLE_CONNECT_ADMIN, - ROLE_ADMINISTRATOR, -} from '../../../../config/constants' +import { hasPermission } from '../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../config/permissions' const moment = extendMoment(Moment) const phaseStatuses = PHASE_STATUS.map(ps => ({ @@ -37,7 +35,7 @@ class EditStageForm extends React.Component { this.state = { isUpdating: false, - isEdittable: (_.get(props, 'phase.status') !== PHASE_STATUS_COMPLETED) || (_.get(props, 'isAdmin')), + isEdittable: (_.get(props, 'phase.status') !== PHASE_STATUS_COMPLETED) || (_.get(props, 'canEditCompletedPhase')), disableActiveStatusFields: _.get(props, 'phase.status') !== PHASE_STATUS_ACTIVE, showPhaseOverlapWarning: false, phaseIsdirty: false, @@ -87,7 +85,7 @@ class EditStageForm extends React.Component { componentWillReceiveProps(nextProps) { this.setState({ isUpdating: nextProps.isUpdating, - isEdittable: nextProps.phase.status !== PHASE_STATUS_COMPLETED || nextProps.isAdmin, + isEdittable: nextProps.phase.status !== PHASE_STATUS_COMPLETED || nextProps.canEditCompletedPhase, disableActiveStatusFields: nextProps.phase.status !== PHASE_STATUS_ACTIVE, }) @@ -407,18 +405,12 @@ EditStageForm.propTypes = { phaseIndex: PT.number } -const mapStateToProps = ({projectState, productsTimelines, loadUser}) => { - const adminRoles = [ - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, - ] - return { - isUpdating: projectState.processing, - phases: projectState.phases, - productsTimelines, - isAdmin: _.intersection(loadUser.user.roles, adminRoles).length > 0 - } -} +const mapStateToProps = ({projectState, productsTimelines}) => ({ + isUpdating: projectState.processing, + phases: projectState.phases, + productsTimelines, + canEditCompletedPhase: hasPermission(PERMISSIONS.MANAGE_COMPLETED_PHASE) +}) const actionCreators = { updatePhaseAction, diff --git a/src/projects/detail/components/PhaseCard/PhaseCard.jsx b/src/projects/detail/components/PhaseCard/PhaseCard.jsx index ae16dccb9..243deb9ac 100644 --- a/src/projects/detail/components/PhaseCard/PhaseCard.jsx +++ b/src/projects/detail/components/PhaseCard/PhaseCard.jsx @@ -29,7 +29,7 @@ import BackIcon from '../../../../assets/icons/arrow-left.svg' import EditStageForm from './EditStageForm' import NotificationsReader from '../../../../components/NotificationsReader' -import PERMISSIONS from '../../../../config/permissions' +import { PERMISSIONS } from '../../../../config/permissions' import {hasPermission} from '../../../../helpers/permissions' import './PhaseCard.scss' diff --git a/src/projects/detail/components/ProjectPlanEmpty/ProjectPlanEmpty.jsx b/src/projects/detail/components/ProjectPlanEmpty/ProjectPlanEmpty.jsx index 55ee301a7..41e8c3755 100644 --- a/src/projects/detail/components/ProjectPlanEmpty/ProjectPlanEmpty.jsx +++ b/src/projects/detail/components/ProjectPlanEmpty/ProjectPlanEmpty.jsx @@ -2,19 +2,20 @@ * A plug to show when project plan doesn't have any phases */ import React from 'react' +import { PERMISSIONS } from '../../../../config/permissions' +import { hasPermission } from '../../../../helpers/permissions' import './ProjectPlanEmpty.scss' -const ProjectPlanEmpty = ({ isManageUser }) => { - - return isManageUser ? ( +const ProjectPlanEmpty = () => { + return hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) ? (

Build Your Project Plan

Build your project plan in Connect to reflect delivery progress to the customer. Begin by clicking the "Add Phase" button, select the template that best matches your need, and modify the phase title and milestone dates prior to publishing to the customer.

Important Note: To move the project into 'Active' status, you must set at least one phase in Connect's Project Plan to be in 'Planned' status, which signifies to customers that delivery planning and execution has begun.

If you feel like you have more things to send over, or want to reach out to us, please drop us a line at support@topcoder.com. Thanks!

- ) : ( + ) : (

Welcome to your project plan

We are reviewing your request. Within the next 24 hours, Topcoder will contact you to discuss next steps, including finalizing your sale and preparing our crowd to meet your needs. Once delivery is mobilized, your project plan will be updated to reflect your detailed delivery plan and will serve as your resource for engaging in key milestones and monitoring progress.

diff --git a/src/projects/detail/components/ProjectScopeDrawer/ProjectScopeDrawer.jsx b/src/projects/detail/components/ProjectScopeDrawer/ProjectScopeDrawer.jsx index 5e8595844..ef5bc32eb 100644 --- a/src/projects/detail/components/ProjectScopeDrawer/ProjectScopeDrawer.jsx +++ b/src/projects/detail/components/ProjectScopeDrawer/ProjectScopeDrawer.jsx @@ -17,6 +17,8 @@ import spinnerWhileLoading from '../../../../components/LoadingSpinner' import EditProjectForm from '../../components/EditProjectForm' import './ProjectScopeDrawer.scss' import { updateSection } from '../../../../helpers/wizardHelper' +import { hasPermission } from '../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../config/permissions' // This handles showing a spinner while the state is being loaded async @@ -58,14 +60,12 @@ class ProjectScopeDrawer extends Component { processing, fireProjectDirty, fireProjectDirtyUndo, - isSuperUser, - currentMemberRole, productTemplates, productCategories, onRequestChange, } = this.props - const editPriv = isSuperUser ? isSuperUser : !!currentMemberRole + const editPriv = hasPermission(PERMISSIONS.EDIT_PROJECT_SPECIFICATION) const attachmentsStorePath = `${PROJECT_ATTACHMENTS_FOLDER}/${project.id}/` const finalSummaryIndex = _.findIndex(_.get(template, 'sections', []), { id: 'summary-final' @@ -122,7 +122,7 @@ class ProjectScopeDrawer extends Component { updateAttachment={this.updateProjectAttachment} removeAttachment={this.removeProjectAttachment} attachmentsStorePath={attachmentsStorePath} - canManageAttachments={!!currentMemberRole} + canManageAttachments={hasPermission(PERMISSIONS.EDIT_PROJECT_SPECIFICATION)} productTemplates={productTemplates} productCategories={productCategories} currentWizardStep={currentWizardStep} diff --git a/src/projects/detail/components/ProjectSpecSidebar.jsx b/src/projects/detail/components/ProjectSpecSidebar.jsx index 43d9fd2dd..c372a9c2b 100644 --- a/src/projects/detail/components/ProjectSpecSidebar.jsx +++ b/src/projects/detail/components/ProjectSpecSidebar.jsx @@ -5,10 +5,11 @@ import { withRouter } from 'react-router-dom' import { connect } from 'react-redux' import cn from 'classnames' import SidebarNav from './SidebarNav' -import { PROJECT_ROLE_OWNER, PROJECT_ROLE_CUSTOMER } from '../../../config/constants' import { updateProject } from '../../actions/project' import ReviewProjectButton from './ReviewProjectButton' import './ProjectSpecSidebar.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const calcProgress = (project, subSection) => { if (subSection.type === 'questions') { @@ -64,7 +65,6 @@ class ProjectSpecSidebar extends Component { shouldComponentUpdate(nextProps) { return !( _.isEqual(this.props.project, nextProps.project) - && _.isEqual(this.props.currentMemberRole, nextProps.currentMemberRole) && _.isEqual(this.props.sections, nextProps.sections) ) } @@ -111,9 +111,8 @@ class ProjectSpecSidebar extends Component { render() { const { navItems, canSubmitForReview } = this.state - const { currentMemberRole, project } = this.props - const showReviewBtn = project.status === 'draft' && - _.indexOf([PROJECT_ROLE_OWNER, PROJECT_ROLE_CUSTOMER], currentMemberRole) > -1 + const { project } = this.props + const showReviewBtn = project.status === 'draft' && hasPermission(PERMISSIONS.SUBMIT_PROJECT_FOR_REVIEW) return (
@@ -147,7 +146,6 @@ class ProjectSpecSidebar extends Component { ProjectSpecSidebar.PropTypes = { project: PropTypes.object.isRequired, sections: PropTypes.arrayOf(PropTypes.object).isRequired, - currentMemberRole: PropTypes.string } const mapDispatchToProps = { updateProject } diff --git a/src/projects/detail/components/ProjectStage.jsx b/src/projects/detail/components/ProjectStage.jsx index 787f6411d..cf225da6d 100644 --- a/src/projects/detail/components/ProjectStage.jsx +++ b/src/projects/detail/components/ProjectStage.jsx @@ -22,6 +22,8 @@ import ProductTimelineContainer from '../containers/ProductTimelineContainer' import PostsContainer from '../../../components/Posts' import NotificationsReader from '../../../components/NotificationsReader' import spinnerWhileLoading from '../../../components/LoadingSpinner' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const enhance = spinnerWhileLoading(props => !props.processing) const EnhancedEditProjectForm = enhance(EditProjectForm) @@ -161,10 +163,7 @@ class ProjectStage extends React.Component{ project, productTemplates, productCategories, - currentMemberRole, isProcessing, - isSuperUser, - isManageUser, updateProduct, fireProductDirty, fireProductDirtyUndo, @@ -210,7 +209,6 @@ class ProjectStage extends React.Component{ deleteProjectPhase(project.id, phase.id)} timeline={timeline} hasUnseen={hasAnyNotifications} @@ -224,8 +222,6 @@ class ProjectStage extends React.Component{ @@ -250,7 +246,7 @@ class ProjectStage extends React.Component{ template={template} productTemplates={productTemplates} productCategories={productCategories} - isEdittable={isSuperUser || !!currentMemberRole} + isEdittable={hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN)} submitHandler={(model) => updateProduct(project.id, phase.id, product.id, model)} saving={isProcessing} fireProjectDirty={(values) => fireProductDirty(phase.id, product.id, values)} @@ -259,7 +255,7 @@ class ProjectStage extends React.Component{ updateAttachment={this.updateProductAttachment} removeAttachment={this.removeProductAttachment} attachmentsStorePath={attachmentsStorePath} - canManageAttachments={!!currentMemberRole} + canManageAttachments={hasPermission(PERMISSIONS.EDIT_PROJECT_SPECIFICATION)} disableAutoScrolling />
@@ -272,7 +268,6 @@ class ProjectStage extends React.Component{ ProjectStage.defaultProps = { activeTab: '', - currentMemberRole: null, } ProjectStage.propTypes = { @@ -283,9 +278,7 @@ ProjectStage.propTypes = { productCategories: PT.array.isRequired, productsTimelines: PT.object, phasesTopics: PT.object, - currentMemberRole: PT.string, isProcessing: PT.bool.isRequired, - isSuperUser: PT.bool.isRequired, updateProduct: PT.func.isRequired, fireProductDirty: PT.func.isRequired, fireProductDirtyUndo: PT.func.isRequired, diff --git a/src/projects/detail/components/ProjectStageTabs/ProjectStageTabs.jsx b/src/projects/detail/components/ProjectStageTabs/ProjectStageTabs.jsx index 65b7e994a..ba62c0d61 100644 --- a/src/projects/detail/components/ProjectStageTabs/ProjectStageTabs.jsx +++ b/src/projects/detail/components/ProjectStageTabs/ProjectStageTabs.jsx @@ -1,14 +1,14 @@ import React from 'react' import GenericMenu from '../../../../components/GenericMenu' +import { PERMISSIONS } from '../../../../config/permissions' +import { hasPermission } from '../../../../helpers/permissions' import './ProjectStageTabs.scss' const ProjectStageTabs = ({ activeTab, hasTimeline, - isManageUser, - isSuperUser, onTabClick, hasNotifications, }) => { @@ -31,7 +31,7 @@ const ProjectStageTabs = ({ }) // show specification tab for everybody expect of customers - if (isManageUser || isSuperUser) { + if (hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN)) { tabs.push({ onClick: () => onTabClick('specification'), label: 'Specification', diff --git a/src/projects/detail/components/ProjectStages.jsx b/src/projects/detail/components/ProjectStages.jsx index bcd0c3aa6..7953ea54f 100644 --- a/src/projects/detail/components/ProjectStages.jsx +++ b/src/projects/detail/components/ProjectStages.jsx @@ -79,17 +79,14 @@ const ProjectStages = ({ productTemplates, productCategories, productsTimelines, - currentMemberRole, phasesTopics, isProcessing, - isSuperUser, updateProduct, fireProductDirty, fireProductDirtyUndo, addProductAttachment, updateProductAttachment, removeProductAttachment, - isManageUser, deleteProjectPhase, expandProjectPhase, collapseProjectPhase, @@ -111,10 +108,7 @@ const ProjectStages = ({ productCategories={productCategories} productsTimelines={productsTimelines} phasesTopics={phasesTopics} - currentMemberRole={currentMemberRole} isProcessing={isProcessing} - isSuperUser={isSuperUser} - isManageUser={isManageUser} project={project} phase={phase} phaseNonDirty={phasesNonDirty[index]} @@ -140,7 +134,6 @@ const ProjectStages = ({ ) ProjectStages.defaultProps = { - currentMemberRole: null, } ProjectStages.propTypes = { @@ -149,9 +142,7 @@ ProjectStages.propTypes = { productCategories: PT.array.isRequired, productsTimelines: PT.object, phasesTopics: PT.object, - currentMemberRole: PT.string, isProcessing: PT.bool.isRequired, - isSuperUser: PT.bool.isRequired, updateProduct: PT.func.isRequired, fireProductDirty: PT.func.isRequired, fireProductDirtyUndo: PT.func.isRequired, diff --git a/src/projects/detail/components/ProjectSummaryReport/ProjectBudgetReport.jsx b/src/projects/detail/components/ProjectSummaryReport/ProjectBudgetReport.jsx index 54d2a75a1..325522c03 100644 --- a/src/projects/detail/components/ProjectSummaryReport/ProjectBudgetReport.jsx +++ b/src/projects/detail/components/ProjectSummaryReport/ProjectBudgetReport.jsx @@ -1,7 +1,7 @@ import React from 'react' import PT from 'prop-types' import { formatNumberWithCommas } from '../../../../helpers/format' -import PERMISSIONS from '../../../../config/permissions' +import { PERMISSIONS } from '../../../../config/permissions' import { hasPermission } from '../../../../helpers/permissions' import './ProjectBudgetReport.scss' diff --git a/src/projects/detail/components/ProjectSummaryReport/ProjectSummaryReport.jsx b/src/projects/detail/components/ProjectSummaryReport/ProjectSummaryReport.jsx index 5d8a5bf03..8afe54f00 100644 --- a/src/projects/detail/components/ProjectSummaryReport/ProjectSummaryReport.jsx +++ b/src/projects/detail/components/ProjectSummaryReport/ProjectSummaryReport.jsx @@ -2,7 +2,7 @@ import React from 'react' import PT from 'prop-types' import ProjectBudgetReport from './ProjectBudgetReport' import TopcoderDifferenceReport from './TopcoderDifferenceReport' -import PERMISSIONS from '../../../../config/permissions' +import { PERMISSIONS } from '../../../../config/permissions' import { hasPermission } from '../../../../helpers/permissions' import './ProjectSummaryReport.scss' diff --git a/src/projects/detail/components/ScopeChangeRequest/ScopeChangeRequest.jsx b/src/projects/detail/components/ScopeChangeRequest/ScopeChangeRequest.jsx index f795d8ef3..4b893d5c2 100644 --- a/src/projects/detail/components/ScopeChangeRequest/ScopeChangeRequest.jsx +++ b/src/projects/detail/components/ScopeChangeRequest/ScopeChangeRequest.jsx @@ -5,6 +5,8 @@ import { flatten } from 'flat' import { SCOPE_CHANGE_REQ_STATUS_PENDING, SCOPE_CHANGE_REQ_STATUS_APPROVED } from '../../../../config/constants' import styles from './ScopeChangeRequest.scss' +import { hasPermission } from '../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../config/permissions' /** * The component that renders the changes in new scope change request from the old scope @@ -137,10 +139,7 @@ class ScopeChangeRequest extends React.Component { render() { const { changes } = this.state const { - isCustomer, isRequestor, - isManager, - isAdmin, pendingScopeChange, onActivate, onApprove, @@ -151,6 +150,9 @@ class ScopeChangeRequest extends React.Component { const status = pendingScopeChange.status const isPending = status === SCOPE_CHANGE_REQ_STATUS_PENDING const isApproved = status === SCOPE_CHANGE_REQ_STATUS_APPROVED + const canApproveReject = hasPermission(PERMISSIONS.APPROVE_REJECT_SCOPE_REQUESTS) + const canActivate = hasPermission(PERMISSIONS.ACTIVATE_SCOPE_REQUESTS) + const canCancelNotOwn = hasPermission(PERMISSIONS.CANCEL_SCOPE_REQUESTS_NOT_OWN) return (
@@ -162,27 +164,27 @@ class ScopeChangeRequest extends React.Component {
))} - {status === SCOPE_CHANGE_REQ_STATUS_APPROVED && isCustomer && ( + {status === SCOPE_CHANGE_REQ_STATUS_APPROVED && canApproveReject && (
Note: This change is awaiting activation.
)}
- {(isCustomer || isAdmin) && isPending && ( + {canApproveReject && isPending && ( )} - {(isCustomer || isAdmin) && isPending && ( + {canApproveReject && isPending && ( )} - {(isManager || isAdmin) && isApproved && ( + {canActivate && isApproved && ( )} - {(isRequestor || isAdmin) && isPending && ( + {(isRequestor || canCancelNotOwn) && isPending && ( @@ -194,10 +196,7 @@ class ScopeChangeRequest extends React.Component { } ScopeChangeRequest.propTypes = { - isCustomer: PropTypes.bool, isRequestor: PropTypes.bool, - isManager: PropTypes.bool, - isAdmin: PropTypes.bool, pendingScopeChange: PropTypes.object.isRequired, template: PropTypes.object.isRequired, onApprove: PropTypes.func, diff --git a/src/projects/detail/components/timeline/Milestone/Milestone.jsx b/src/projects/detail/components/timeline/Milestone/Milestone.jsx index cc32e09bd..e92e9cd0c 100644 --- a/src/projects/detail/components/timeline/Milestone/Milestone.jsx +++ b/src/projects/detail/components/timeline/Milestone/Milestone.jsx @@ -28,7 +28,7 @@ import XMartIcon from '../../../../../assets/icons/x-mark.svg' import { MILESTONE_STATUS, SCREEN_BREAKPOINT_MD } from '../../../../../config/constants' -import PERMISSIONS from '../../../../../config/permissions' +import { PERMISSIONS } from '../../../../../config/permissions' import {hasPermission} from '../../../../../helpers/permissions' import './Milestone.scss' @@ -103,17 +103,17 @@ class Milestone extends React.Component { } isActualStartDateEditable() { - const { milestone, currentUser } = this.props + const { milestone } = this.props const isActive = milestone.status === MILESTONE_STATUS.ACTIVE const isCompleted = milestone.status === MILESTONE_STATUS.COMPLETED - return (isActive || isCompleted) && currentUser.isAdmin + return (isActive || isCompleted) && hasPermission(PERMISSIONS.EDIT_MILESTONE_ACTUAL_START_COMPLETION_DATES) } isCompletionDateEditable() { - const { milestone, currentUser } = this.props + const { milestone } = this.props const isCompleted = milestone.status === MILESTONE_STATUS.COMPLETED - return isCompleted && currentUser.isAdmin + return isCompleted && hasPermission(PERMISSIONS.EDIT_MILESTONE_ACTUAL_START_COMPLETION_DATES) } updateMilestoneWithData(values) { diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypeAddLinks/MilestoneTypeAddLinks.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypeAddLinks/MilestoneTypeAddLinks.jsx index 51471a8f8..d5c22cd9d 100644 --- a/src/projects/detail/components/timeline/milestones/MilestoneTypeAddLinks/MilestoneTypeAddLinks.jsx +++ b/src/projects/detail/components/timeline/milestones/MilestoneTypeAddLinks/MilestoneTypeAddLinks.jsx @@ -19,6 +19,8 @@ import { } from '../../../../../../config/constants' import './MilestoneTypeAddLinks.scss' +import { hasPermission } from '../../../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../../../config/permissions' class MilestoneTypeAddLinks extends React.Component { constructor(props) { @@ -114,7 +116,6 @@ class MilestoneTypeAddLinks extends React.Component { const { milestone, theme, - currentUser, extensionRequestDialog, extensionRequestButton, extensionRequestConfirmation, @@ -137,6 +138,9 @@ class MilestoneTypeAddLinks extends React.Component { const progressPercent = getProgressPercent(totalDays, daysLeft) const { showExtensionRequestSection } = this.state + + const canManage = hasPermission(PERMISSIONS.MANAGE_MILESTONE) + return (
@@ -149,7 +153,7 @@ class MilestoneTypeAddLinks extends React.Component { {isActive && (
- +
- {!currentUser.isCustomer && ( + {canManage && ( +
{extensionRequestConfirmation}
@@ -226,19 +230,19 @@ class MilestoneTypeAddLinks extends React.Component { !isCompleted && !extensionRequestDialog && !isShowCompleteConfirmMessage && - !currentUser.isCustomer && showExtensionRequestSection && + canManage && showExtensionRequestSection && (
- {(!currentUser.isCustomer) && ( + {canManage && ( )} - {!currentUser.isCustomer && extensionRequestButton} + {canManage && extensionRequestButton}
)} @@ -256,7 +260,6 @@ MilestoneTypeAddLinks.defaultProps = { MilestoneTypeAddLinks.propTypes = { completeMilestone: PT.func.isRequired, - currentUser: PT.object.isRequired, milestone: PT.object.isRequired, theme: PT.string, updateMilestoneContent: PT.func.isRequired, diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypeCheckpointReview/MilestoneTypeCheckpointReview.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypeCheckpointReview/MilestoneTypeCheckpointReview.jsx index e34e2c4bf..2042bdd31 100644 --- a/src/projects/detail/components/timeline/milestones/MilestoneTypeCheckpointReview/MilestoneTypeCheckpointReview.jsx +++ b/src/projects/detail/components/timeline/milestones/MilestoneTypeCheckpointReview/MilestoneTypeCheckpointReview.jsx @@ -20,6 +20,8 @@ import { } from '../../../../../../config/constants' import './MilestoneTypeCheckpointReview.scss' +import { hasPermission } from '../../../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../../../config/permissions' class MilestoneTypeCheckpointReview extends React.Component { constructor(props) { @@ -193,7 +195,6 @@ class MilestoneTypeCheckpointReview extends React.Component { const { milestone, theme, - currentUser, extensionRequestDialog, extensionRequestButton, extensionRequestConfirmation, @@ -228,6 +229,8 @@ class MilestoneTypeCheckpointReview extends React.Component { const progressPercent = getProgressPercent(totalDays, daysLeft) const waitingForCustomer = _.get(milestone, 'details.metadata.waitingForCustomer', true) + + const canManage = hasPermission(PERMISSIONS.MANAGE_MILESTONE) return (
@@ -248,7 +251,7 @@ class MilestoneTypeCheckpointReview extends React.Component { theme={daysLeft < 0 ? 'warning' : 'light'} readyForReview > - {!currentUser.isCustomer && !isInReview && ( + {canManage && !isInReview && ( )} - {!currentUser.isCustomer && !waitingForCustomer && extensionRequestButton} + {canManage && !waitingForCustomer && extensionRequestButton}
)} @@ -415,7 +418,6 @@ MilestoneTypeCheckpointReview.defaultProps = { MilestoneTypeCheckpointReview.propTypes = { completeMilestone: PT.func.isRequired, - currentUser: PT.object.isRequired, milestone: PT.object.isRequired, theme: PT.string, updateMilestoneContent: PT.func.isRequired, diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypeDelivery/MilestoneTypeDelivery.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypeDelivery/MilestoneTypeDelivery.jsx index 4efc011fa..3a87b402c 100644 --- a/src/projects/detail/components/timeline/milestones/MilestoneTypeDelivery/MilestoneTypeDelivery.jsx +++ b/src/projects/detail/components/timeline/milestones/MilestoneTypeDelivery/MilestoneTypeDelivery.jsx @@ -13,10 +13,11 @@ import MilestonePostEditText from '../../MilestonePostEditText' import MilestoneDescription from '../../MilestoneDescription' import MilestoneDelayNotification from '../../MilestoneDelayNotification' import { getMilestoneStatusText } from '../../../../../../helpers/milestoneHelper' - +import { hasPermission } from '../../../../../../helpers/permissions' import { MILESTONE_STATUS } from '../../../../../../config/constants' import './MilestoneTypeDelivery.scss' +import { PERMISSIONS } from '../../../../../../config/permissions' /* Acceptance dialogue messages based on the milestone type @@ -182,7 +183,7 @@ class MilestoneTypeDelivery extends React.Component { } render() { - const { milestone, theme, currentUser, previousMilestone } = this.props + const { milestone, theme, previousMilestone } = this.props const { isShowFinalFixesRequestForm, finalFixRequests } = this.state const isAccepted = _.get(milestone, 'details.content.isAccepted', false) const isDeclined = _.get(milestone, 'details.content.isDeclined', false) @@ -214,13 +215,14 @@ class MilestoneTypeDelivery extends React.Component { links = _.get(milestone, 'details.content.links', []) } + const canAcceptFinalDelivery = hasPermission(PERMISSIONS.ACCEPT_MILESTONE_FINAL_DELIVERY) + const shouldhaveSecondDot = isActive && (!isAccepted && !isDeclined && !isShowFinalFixesRequestForm && - (currentUser.isCopilot || currentUser.isManager) && - !currentUser.isAdmin && + !canAcceptFinalDelivery && !isFinalFixesSubmitted) const shouldHaveThirdDot = @@ -228,7 +230,7 @@ class MilestoneTypeDelivery extends React.Component { (!isAccepted && !isDeclined && !isShowFinalFixesRequestForm && - (currentUser.isCustomer || currentUser.isAdmin) && + canAcceptFinalDelivery && !isFinalFixesSubmitted) const shouldHaveFourthDot = @@ -239,7 +241,7 @@ class MilestoneTypeDelivery extends React.Component { const shouldHaveFifthDot = isActive && (isAccepted && - !currentUser.isCustomer) + !canAcceptFinalDelivery) const shouldHaveSixthDot = isCompleted @@ -267,8 +269,7 @@ class MilestoneTypeDelivery extends React.Component { !isAccepted && !isDeclined && !isShowFinalFixesRequestForm && - (currentUser.isCopilot || currentUser.isManager) && - !currentUser.isAdmin && + !canAcceptFinalDelivery && !isFinalFixesSubmitted && ( @@ -288,7 +289,7 @@ class MilestoneTypeDelivery extends React.Component { !isAccepted && !isDeclined && !isShowFinalFixesRequestForm && - (currentUser.isCustomer || currentUser.isAdmin) && + canAcceptFinalDelivery && !isFinalFixesSubmitted && ( @@ -340,7 +341,7 @@ class MilestoneTypeDelivery extends React.Component { {(isAccepted) && (
- {!currentUser.isCustomer && ( + {!canAcceptFinalDelivery && ( - {currentUser.isCustomer ? ( + {canAcceptFinalDelivery ? ( ) : ( @@ -278,7 +281,7 @@ class MilestoneTypeFinalDesigns extends React.Component { theme={daysLeft < 0 ? 'warning' : 'light'} readyForReview > - {!isInReview && !currentUser.isCustomer && ( + {!isInReview && canManage && ( )} - {!currentUser.isCustomer && !waitingForCustomer && extensionRequestButton} + {canManage && !waitingForCustomer && extensionRequestButton}
)} @@ -470,7 +473,6 @@ MilestoneTypeFinalDesigns.defaultProps = { MilestoneTypeFinalDesigns.propTypes = { completeMilestone: PT.func.isRequired, - currentUser: PT.object.isRequired, milestone: PT.object.isRequired, theme: PT.string, updateMilestoneContent: PT.func.isRequired, diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypeFinalFixes/MilestoneTypeFinalFixes.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypeFinalFixes/MilestoneTypeFinalFixes.jsx index 10082020e..7f8acfdbf 100644 --- a/src/projects/detail/components/timeline/milestones/MilestoneTypeFinalFixes/MilestoneTypeFinalFixes.jsx +++ b/src/projects/detail/components/timeline/milestones/MilestoneTypeFinalFixes/MilestoneTypeFinalFixes.jsx @@ -17,6 +17,8 @@ import { MILESTONE_STATUS } from '../../../../../../config/constants' import { getMilestoneStatusText, getDaysLeft, getTotalDays, getProgressPercent } from '../../../../../../helpers/milestoneHelper' import './MilestoneTypeFinalFixes.scss' +import { hasPermission } from '../../../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../../../config/permissions' class MilestoneTypeFinalFixes extends React.Component { constructor(props) { @@ -86,7 +88,6 @@ class MilestoneTypeFinalFixes extends React.Component { const { milestone, theme, - currentUser, extensionRequestDialog, extensionRequestButton, extensionRequestConfirmation, @@ -104,6 +105,7 @@ class MilestoneTypeFinalFixes extends React.Component { const progressPercent = getProgressPercent(totalDays, daysLeft) const { showExtensionRequestSection } = this.state + const canManage = hasPermission(PERMISSIONS.MANAGE_MILESTONE) return (
@@ -134,7 +136,7 @@ class MilestoneTypeFinalFixes extends React.Component { ))} - {isActive && !currentUser.isCustomer && ( + {isActive && canManage && (
diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypePhaseSpecification/MilestoneTypePhaseSpecification.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypePhaseSpecification/MilestoneTypePhaseSpecification.jsx index d92e16968..5711f2223 100644 --- a/src/projects/detail/components/timeline/milestones/MilestoneTypePhaseSpecification/MilestoneTypePhaseSpecification.jsx +++ b/src/projects/detail/components/timeline/milestones/MilestoneTypePhaseSpecification/MilestoneTypePhaseSpecification.jsx @@ -16,6 +16,8 @@ import { MILESTONE_STATUS } from '../../../../../../config/constants' import { getMilestoneStatusText } from '../../../../../../helpers/milestoneHelper' import './MilestoneTypePhaseSpecification.scss' +import { hasPermission } from '../../../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../../../config/permissions' class MilestoneTypePhaseSpecification extends React.Component { constructor(props) { @@ -84,7 +86,6 @@ class MilestoneTypePhaseSpecification extends React.Component { const { milestone, theme, - currentUser, extensionRequestDialog, extensionRequestButton, extensionRequestConfirmation, @@ -96,9 +97,11 @@ class MilestoneTypePhaseSpecification extends React.Component { // can add only one specification link const canAddLink = links.length < 1 const { showExtensionRequestSection } = this.state + + const canManage = hasPermission(PERMISSIONS.MANAGE_MILESTONE) return (
- + @@ -107,9 +110,9 @@ class MilestoneTypePhaseSpecification extends React.Component { */} {isActive && (
- + - {!currentUser.isCustomer && ( + {canManage && ( +
{extensionRequestConfirmation}
@@ -147,7 +150,7 @@ class MilestoneTypePhaseSpecification extends React.Component { )} { - !currentUser.isCustomer && + canManage && !extensionRequestDialog && showExtensionRequestSection && ( @@ -159,7 +162,7 @@ class MilestoneTypePhaseSpecification extends React.Component { > Mark as completed - {!currentUser.isCustomer && extensionRequestButton} + {canManage && extensionRequestButton}
@@ -188,7 +191,6 @@ MilestoneTypePhaseSpecification.defaultProps = { MilestoneTypePhaseSpecification.propTypes = { completeMilestone: PT.func.isRequired, - currentUser: PT.object.isRequired, milestone: PT.object.isRequired, theme: PT.string, updateMilestoneContent: PT.func.isRequired, diff --git a/src/projects/detail/components/timeline/milestones/MilestoneTypeProgress/MilestoneTypeProgress.jsx b/src/projects/detail/components/timeline/milestones/MilestoneTypeProgress/MilestoneTypeProgress.jsx index 56ed74bd1..1b580ebd3 100644 --- a/src/projects/detail/components/timeline/milestones/MilestoneTypeProgress/MilestoneTypeProgress.jsx +++ b/src/projects/detail/components/timeline/milestones/MilestoneTypeProgress/MilestoneTypeProgress.jsx @@ -16,6 +16,8 @@ import { MILESTONE_STATUS } from '../../../../../../config/constants' import { getMilestoneStatusText, getDaysLeft, getProgressPercent, getTotalDays } from '../../../../../../helpers/milestoneHelper' import './MilestoneTypeProgress.scss' +import { hasPermission } from '../../../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../../../config/permissions' class MilestoneTypeProgress extends React.Component { constructor(props) { @@ -81,7 +83,6 @@ class MilestoneTypeProgress extends React.Component { const { milestone, theme, - currentUser, extensionRequestDialog, extensionRequestButton, extensionRequestConfirmation, @@ -100,6 +101,8 @@ class MilestoneTypeProgress extends React.Component { const progressPercent = getProgressPercent(totalDays, daysLeft) const { showExtensionRequestSection } = this.state + + const canManage = hasPermission(PERMISSIONS.MANAGE_MILESTONE) return (
@@ -112,7 +115,7 @@ class MilestoneTypeProgress extends React.Component { {isActive && (
- +
- {!currentUser.isCustomer && ( + {canManage && ( +
{extensionRequestConfirmation}
@@ -159,7 +162,7 @@ class MilestoneTypeProgress extends React.Component { )} { - !currentUser.isCustomer && + canManage && !extensionRequestDialog && showExtensionRequestSection && ( @@ -171,7 +174,7 @@ class MilestoneTypeProgress extends React.Component { > Mark as completed - {!currentUser.isCustomer && extensionRequestButton} + {canManage && extensionRequestButton}
@@ -198,7 +201,6 @@ MilestoneTypeProgress.defaultProps = { MilestoneTypeProgress.propTypes = { completeMilestone: PT.func.isRequired, - currentUser: PT.object.isRequired, milestone: PT.object.isRequired, theme: PT.string, updateMilestoneContent: PT.func.isRequired, diff --git a/src/projects/detail/containers/AssetsInfoContainer.jsx b/src/projects/detail/containers/AssetsInfoContainer.jsx index 016fad429..6f01c2357 100644 --- a/src/projects/detail/containers/AssetsInfoContainer.jsx +++ b/src/projects/detail/containers/AssetsInfoContainer.jsx @@ -30,7 +30,7 @@ import { ATTACHMENT_TYPE_FILE, } from '../../../config/constants' import AddLink from '../../../components/AssetsLibrary/AddLink' -import PERMISSIONS from '../../../config/permissions' +import { PERMISSIONS } from '../../../config/permissions' import { hasPermission } from '../../../helpers/permissions' import { addProjectAttachment, updateProjectAttachment, uploadProjectAttachments, discardAttachments, changeAttachmentPermission, @@ -494,8 +494,7 @@ class AssetsInfoContainer extends React.Component { } getLinksAndAttachments() { - const { project, isSuperUser, phases, feeds, - isManageUser, phasesTopics, canAccessPrivatePosts } = this.props + const { project, phases, feeds, phasesTopics, canAccessPrivatePosts } = this.props let attachments = _.filter(project.attachments, a => a.type === ATTACHMENT_TYPE_FILE) // merges the product attachments to show in the links menu @@ -526,7 +525,7 @@ class AssetsInfoContainer extends React.Component { // get list of phase topic in same order as phases // note: for old projects which doesn't have phases we return an empty array const visiblePhases = phases && phases.filter((phase) => ( - isSuperUser || isManageUser || phase.status !== PHASE_STATUS_DRAFT + hasPermission(PERMISSIONS.VIEW_DRAFT_PHASES) || phase.status !== PHASE_STATUS_DRAFT )) || [] const phaseFeeds = _.compact( @@ -707,12 +706,12 @@ class AssetsInfoContainer extends React.Component { } render() { - const { project, currentMemberRole, isSuperUser, projectTemplates, hideLinks, + const { project, projectTemplates, hideLinks, attachmentsAwaitingPermission, addProjectAttachment, discardAttachments, attachmentPermissions, attachmentTags, changeAttachmentPermission, projectMembers, loggedInUser, isSharingAttachment, assetsMembers } = this.props const { ifModalOpen } = this.state - const canManageLinks = !!currentMemberRole || isSuperUser + const canManageLinks = hasPermission(PERMISSIONS.MANAGE_PROJECT_ASSETS) let devices = [] const primaryTarget = _.get(project, 'details.appDefinition.primaryTarget') @@ -805,7 +804,7 @@ class AssetsInfoContainer extends React.Component { let showAddNewButton = false if (activeAssetsType === 'Files' && enableFileUpload) { showAddNewButton = true - } else if (activeAssetsType === 'Links' && canManageLinks) { + } else if (activeAssetsType === 'Links') { showAddNewButton = true } @@ -913,13 +912,10 @@ class AssetsInfoContainer extends React.Component { } AssetsInfoContainer.PropTypes = { - currentMemberRole: PropTypes.string, phases: PropTypes.array, feeds: PropTypes.array, phasesTopics: PropTypes.array, project: PropTypes.object.isRequired, - isSuperUser: PropTypes.bool, - isManageUser: PropTypes.bool, canAccessPrivatePosts: PropTypes.bool.isRequired, } diff --git a/src/projects/detail/containers/AssetsLibraryContainer.jsx b/src/projects/detail/containers/AssetsLibraryContainer.jsx index e2fbd26cb..ba55cd31a 100644 --- a/src/projects/detail/containers/AssetsLibraryContainer.jsx +++ b/src/projects/detail/containers/AssetsLibraryContainer.jsx @@ -29,7 +29,7 @@ import { EVENT_TYPE, } from '../../../config/constants' import Sticky from '../../../components/Sticky' -import PERMISSIONS from '../../../config/permissions' +import { PERMISSIONS } from '../../../config/permissions' import { hasPermission } from '../../../helpers/permissions' import './AssetsLibraryContainer.scss' @@ -58,7 +58,7 @@ class AssetsLibraryContainer extends React.Component { // if the user is a customer and its not a direct link to a particular phase // then by default expand all phases which are active - if (_.isEmpty(location.hash) && this.props.isCustomerUser) { + if (_.isEmpty(location.hash) && hasPermission(PERMISSIONS.EXPAND_ACTIVE_PHASES_BY_DEFAULT)) { _.forEach(this.props.phases, phase => { if (phase.status === PHASE_STATUS_ACTIVE) { expandProjectPhase(phase.id) @@ -76,9 +76,6 @@ class AssetsLibraryContainer extends React.Component { render() { const { project, - isSuperUser, - isManageUser, - currentMemberRole, feeds, isFeedsLoading, phases, @@ -93,11 +90,8 @@ class AssetsLibraryContainer extends React.Component { const leftArea = ( @@ -147,10 +138,7 @@ class AssetsLibraryContainer extends React.Component { } AssetsLibraryContainer.propTypes = { - currentMemberRole: PT.string.isRequired, isProcessing: PT.bool.isRequired, - isSuperUser: PT.bool.isRequired, - isManageUser: PT.bool.isRequired, project: PT.object.isRequired, phases: PT.array.isRequired, productsTimelines: PT.object.isRequired, diff --git a/src/projects/detail/containers/DashboardContainer.jsx b/src/projects/detail/containers/DashboardContainer.jsx index f361fc6f7..3ad4d8558 100644 --- a/src/projects/detail/containers/DashboardContainer.jsx +++ b/src/projects/detail/containers/DashboardContainer.jsx @@ -39,7 +39,7 @@ import ProjectPlanEmpty from '../components/ProjectPlanEmpty' import NotificationsReader from '../../../components/NotificationsReader' import { hasPermission } from '../../../helpers/permissions' import { getProjectTemplateById } from '../../../helpers/templates' -import PERMISSIONS from '../../../config/permissions' +import { PERMISSIONS } from '../../../config/permissions' import { updateProject, fireProjectDirty, fireProjectDirtyUndo } from '../../actions/project' import { addProjectAttachment, updateProjectAttachment, removeProjectAttachment } from '../../actions/projectAttachment' import ProjectEstimation from '../../create/components/ProjectEstimation' @@ -85,21 +85,10 @@ class DashboardContainer extends React.Component { } componentDidMount() { + const { expandProjectPhase } = this.props // if the user is a customer and its not a direct link to a particular phase // then by default expand all phases which are active - const { isCustomerUser, expandProjectPhase, location } = this.props - - if (isCustomerUser) { - _.forEach(this.props.phases, phase => { - if (phase.status === PHASE_STATUS_ACTIVE) { - expandProjectPhase(phase.id) - } - }) - } - - // if the user is a customer and its not a direct link to a particular phase - // then by default expand all phases which are active - if (_.isEmpty(location.hash) && this.props.isCustomerUser) { + if (_.isEmpty(location.hash) && hasPermission(PERMISSIONS.EXPAND_ACTIVE_PHASES_BY_DEFAULT)) { _.forEach(this.props.phases, phase => { if (phase.status === PHASE_STATUS_ACTIVE) { expandProjectPhase(phase.id) @@ -126,9 +115,6 @@ class DashboardContainer extends React.Component { phases, phasesNonDirty, isLoadingPhases, - currentMemberRole, - isSuperUser, - isManageUser, notifications, productTemplates, projectTemplates, @@ -165,7 +151,7 @@ class DashboardContainer extends React.Component { // manager user sees all phases // customer user doesn't see unplanned (draft) phases const visiblePhases = phases && phases.filter((phase) => ( - isSuperUser || isManageUser || phase.status !== PHASE_STATUS_DRAFT + hasPermission(PERMISSIONS.VIEW_DRAFT_PHASES) || phase.status !== PHASE_STATUS_DRAFT )) const visiblePhasesIds = _.map(visiblePhases, 'id') const visiblePhasesNonDirty = phasesNonDirty && phasesNonDirty.filter((phaseNonDirty) => ( @@ -177,11 +163,8 @@ class DashboardContainer extends React.Component { const leftArea = ( this.setState({open})} - isSuperUser={isSuperUser} project={project} template={template} updateProject={updateProject} @@ -257,7 +239,6 @@ class DashboardContainer extends React.Component { addProjectAttachment={addProjectAttachment} updateProjectAttachment={updateProjectAttachment} removeProjectAttachment={removeProjectAttachment} - currentMemberRole={currentMemberRole} productTemplates={productTemplates} productCategories={productCategories} /> @@ -271,7 +252,7 @@ class DashboardContainer extends React.Component { }} /> ) : ( - + )} {isProjectLive && hasPermission(PERMISSIONS.MANAGE_PROJECT_PLAN) && !isLoadingPhases && (
Add New Phase diff --git a/src/projects/detail/containers/FeedContainer.js b/src/projects/detail/containers/FeedContainer.js index 4ab05e360..9f4787d66 100644 --- a/src/projects/detail/containers/FeedContainer.js +++ b/src/projects/detail/containers/FeedContainer.js @@ -34,7 +34,7 @@ import SingleFeedContainer from './SingleFeedContainer' import { isSystemUser } from '../../../helpers/tcHelpers' import { hasPermission } from '../../../helpers/permissions' -import PERMISSIONS from '../../../config/permissions' +import { PERMISSIONS } from '../../../config/permissions' import './FeedContainer.scss' @@ -114,7 +114,7 @@ class FeedView extends React.Component { } mapFeed(feed, showAll = false, resetNewComment = false, prevProps, currentProps) { - const { allMembers, project, currentMemberRole } = this.props + const { allMembers, project } = this.props const item = _.pick(feed, ['id', 'date', 'read', 'tag', 'title', 'totalPosts', 'userId', 'reference', 'referenceId', 'postIds', 'isSavingTopic', 'isDeletingTopic', 'isAddingComment', 'isLoadingComments', 'error']) // Github issue##623, allow comments on all posts (including system posts) item.allowComments = true @@ -123,7 +123,7 @@ class FeedView extends React.Component { } else { item.user = allMembers[item.userId] } - item.unread = !feed.read && !!currentMemberRole + item.unread = !feed.read item.totalComments = feed.totalPosts item.comments = [] let prevFeed = null @@ -142,7 +142,7 @@ class FeedView extends React.Component { isSavingComment: p.isSavingComment, isDeletingComment: p.isDeletingComment, error: p.error, - unread: !p.read && !!currentMemberRole, + unread: !p.read, date, createdAt: p.date, edited, @@ -382,7 +382,7 @@ class FeedView extends React.Component { } render () { - const {currentUser, currentMemberRole, isCreatingFeed, error, allMembers, + const {currentUser, isCreatingFeed, error, allMembers, toggleNotificationRead, notifications, project, projectMembers, canAccessPrivatePosts, inTopicDrawer, onDrawerClose, isFeedsLoading } = this.props const { feeds, isNewPostMobileOpen } = this.state @@ -394,7 +394,7 @@ class FeedView extends React.Component { item.isActive)[0] const onLeaveMessage = this.onLeave() || '' const renderRightPanel = () => { - if (!!currentMemberRole && (isCreateNewMessage || !threads.length)) { + if (hasPermission(PERMISSIONS.CREATE_TOPICS) && (isCreateNewMessage || !threads.length)) { return ( diff --git a/src/projects/detail/containers/MessagesTabContainer.jsx b/src/projects/detail/containers/MessagesTabContainer.jsx index 6014922c6..5a98ddc52 100644 --- a/src/projects/detail/containers/MessagesTabContainer.jsx +++ b/src/projects/detail/containers/MessagesTabContainer.jsx @@ -29,7 +29,7 @@ import { createProjectTopic } from '../../actions/projectTopics' -import PERMISSIONS from '../../../config/permissions' +import { PERMISSIONS } from '../../../config/permissions' import { hasPermission } from '../../../helpers/permissions' import { @@ -126,11 +126,8 @@ class MessagesTabContainer extends React.Component { getSidebarContent() { const { location, - currentMemberRole, project, phases, - isSuperUser, - isManageUser, feeds, isFeedsLoading, productsTimelines, @@ -141,11 +138,8 @@ class MessagesTabContainer extends React.Component { const leftArea = ( diff --git a/src/projects/detail/containers/ProductTimelineContainer.jsx b/src/projects/detail/containers/ProductTimelineContainer.jsx index c7a8970bf..c2dba83fa 100644 --- a/src/projects/detail/containers/ProductTimelineContainer.jsx +++ b/src/projects/detail/containers/ProductTimelineContainer.jsx @@ -24,13 +24,6 @@ import { submitFinalFixesRequest, } from '../../actions/productsTimelines' -import { - ROLE_CONNECT_COPILOT, - ROLE_CONNECT_MANAGER, - ROLE_CONNECT_ADMIN, - ROLE_ADMINISTRATOR, -} from '../../../config/constants' - class ProductTimelineContainer extends React.Component { constructor(props) { super(props) @@ -52,10 +45,6 @@ class ProductTimelineContainer extends React.Component { ProductTimelineContainer.propTypes = { currentUser: PT.shape({ userId: PT.number.isRequired, - isCopilot: PT.bool.isRequired, - isManager: PT.bool.isRequired, - isAdmin: PT.bool.isRequired, - isCustomer: PT.bool.isRequired, }).isRequired, isLoading: PT.bool, timeline: PT.object, @@ -64,32 +53,14 @@ ProductTimelineContainer.propTypes = { extendProductMilestone: PT.func.isRequired, } -const mapStateToProps = ({ productsTimelines, loadUser }, props) => { - const adminRoles = [ - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, - ] - - const powerUserRoles = [ - ROLE_CONNECT_COPILOT, - ROLE_CONNECT_MANAGER, - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, - ] - - return { - timeline: _.get(productsTimelines[props.product.id], 'timeline'), - isLoading: _.get(productsTimelines[props.product.id], 'isLoading', false), - phaseId: props.product.phaseId, - currentUser: { - userId: parseInt(loadUser.user.id, 10), - isCopilot: _.includes(loadUser.user.roles, ROLE_CONNECT_COPILOT), - isManager: _.includes(loadUser.user.roles, ROLE_CONNECT_MANAGER), - isAdmin: _.intersection(loadUser.user.roles, adminRoles).length > 0, - isCustomer: _.intersection(loadUser.user.roles, powerUserRoles).length === 0, - }, - } -} +const mapStateToProps = ({ productsTimelines, loadUser }, props) => ({ + timeline: _.get(productsTimelines[props.product.id], 'timeline'), + isLoading: _.get(productsTimelines[props.product.id], 'isLoading', false), + phaseId: props.product.phaseId, + currentUser: { + userId: parseInt(loadUser.user.id, 10), + }, +}) const mapDispatchToProps = { updateProductMilestone, diff --git a/src/projects/detail/containers/ProjectFAQContainer.jsx b/src/projects/detail/containers/ProjectFAQContainer.jsx index dfc7a2678..e6e8fc984 100644 --- a/src/projects/detail/containers/ProjectFAQContainer.jsx +++ b/src/projects/detail/containers/ProjectFAQContainer.jsx @@ -8,7 +8,7 @@ import TwoColsLayout from '../../../components/TwoColsLayout' import FAQContainer from '../../../components/FAQ/FAQContainer' import ProjectInfoContainer from './ProjectInfoContainer' import { hasPermission } from '../../../helpers/permissions' -import PERMISSIONS from '../../../config/permissions' +import { PERMISSIONS } from '../../../config/permissions' class ProjectFAQContainer extends Component { @@ -17,9 +17,6 @@ class ProjectFAQContainer extends Component { project, projectTemplate, phases, - currentMemberRole, - isSuperUser, - isManageUser, isProcessing, feeds, isFeedsLoading, @@ -31,11 +28,8 @@ class ProjectFAQContainer extends Component { const leftArea = ( { const mapDispatchToProps = { } -export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ProjectFAQContainer)) \ No newline at end of file +export default connect(mapStateToProps, mapDispatchToProps)(withRouter(ProjectFAQContainer)) diff --git a/src/projects/detail/containers/ProjectInfoContainer.js b/src/projects/detail/containers/ProjectInfoContainer.js index 1cfb6898f..28e40a04c 100644 --- a/src/projects/detail/containers/ProjectInfoContainer.js +++ b/src/projects/detail/containers/ProjectInfoContainer.js @@ -12,9 +12,6 @@ import { loadProjectPlan } from '../../actions/projectPlan' import { getProjectNavLinks } from '../../../helpers/projectHelper' import { getProjectTemplateByKey, containsFAQ } from '../../../helpers/templates' import { - PROJECT_ROLE_OWNER, - PROJECT_ROLE_COPILOT, - PROJECT_ROLE_MANAGER, DIRECT_PROJECT_URL, SALESFORCE_PROJECT_LEAD_LINK, PROJECT_STATUS_CANCELLED, @@ -22,11 +19,8 @@ import { PROJECT_STATUS_COMPLETED, PHASE_STATUS_REVIEWED, PHASE_STATUS_ACTIVE, - PROJECT_ROLE_PROJECT_MANAGER, - PROJECT_ROLE_PROGRAM_MANAGER, - PROJECT_ROLE_SOLUTION_ARCHITECT, } from '../../../config/constants' -import PERMISSIONS from '../../../config/permissions' +import { PERMISSIONS } from '../../../config/permissions' import { hasPermission } from '../../../helpers/permissions' import Panel from '../../../components/Panel/Panel' import ProjectInfo from '../../../components/ProjectInfo/ProjectInfo' @@ -425,7 +419,7 @@ class ProjectInfoContainer extends React.Component { render() { const { showDeleteConfirm } = this.state - const { project, currentMemberRole, isSuperUser, phases, hideInfo, hideMembers, + const { project, phases, hideInfo, hideMembers, productsTimelines, isProjectProcessing, notifications, projectTemplates } = this.props const projectTemplateId = project.templateId @@ -436,15 +430,7 @@ class ProjectInfoContainer extends React.Component { // const isTaaS = PROJECT_CATEGORY_TAAS === projectTemplate.category let directLinks = null - // check if direct links need to be added - const isMemberOrCopilot = _.indexOf([ - PROJECT_ROLE_COPILOT, - PROJECT_ROLE_MANAGER, - PROJECT_ROLE_PROJECT_MANAGER, - PROJECT_ROLE_PROGRAM_MANAGER, - PROJECT_ROLE_SOLUTION_ARCHITECT - ], currentMemberRole) > -1 - if (isMemberOrCopilot || isSuperUser) { + if (hasPermission(PERMISSIONS.VIEW_PROJECT_SPECIAL_LINKS)) { directLinks = [] // if(!isTaaS) // directLinks.push({name: 'Launch Work Manager', href: `${WORK_MANAGER_APP}/${project.id}/challenges`}) @@ -456,7 +442,7 @@ class ProjectInfoContainer extends React.Component { directLinks.push({name: 'Salesforce Lead', href: `${SALESFORCE_PROJECT_LEAD_LINK}${project.id}`}) } - const canDeleteProject = currentMemberRole === PROJECT_ROLE_OWNER && project.status === 'draft' + const canDeleteProject = hasPermission(PERMISSIONS.DELETE_DRAFT_PROJECT) && project.status === 'draft' const projectNotReadNotifications = filterReadNotifications(filterNotificationsByProjectId(notifications, project.id)) const notReadMessageNotifications = filterTopicAndPostChangedNotifications(projectNotReadNotifications, /^(?:MESSAGES|PRIMARY)$/) @@ -482,14 +468,7 @@ class ProjectInfoContainer extends React.Component { }) const canEdit = ( - project.status !== PROJECT_STATUS_COMPLETED && (isSuperUser || (currentMemberRole - && (_.indexOf([ - PROJECT_ROLE_COPILOT, - PROJECT_ROLE_MANAGER, - PROJECT_ROLE_PROJECT_MANAGER, - PROJECT_ROLE_PROGRAM_MANAGER, - PROJECT_ROLE_SOLUTION_ARCHITECT - ], currentMemberRole) > -1))) + project.status !== PROJECT_STATUS_COMPLETED && hasPermission(PERMISSIONS.EDIT_PROJECT_STATUS) ) const progress = _.get(process, 'percent', 0) @@ -522,12 +501,10 @@ class ProjectInfoContainer extends React.Component { -1 - const isManager = currentMemberRole && [ - PROJECT_ROLE_MANAGER, - PROJECT_ROLE_ACCOUNT_MANAGER, - PROJECT_ROLE_ACCOUNT_EXECUTIVE, - PROJECT_ROLE_PROJECT_MANAGER, - PROJECT_ROLE_PROGRAM_MANAGER, - PROJECT_ROLE_SOLUTION_ARCHITECT - ].indexOf(currentMemberRole) > -1 + const editPriv = hasPermission(PERMISSIONS.EDIT_PROJECT_SPECIFICATION) const attachmentsStorePath = `${PROJECT_ATTACHMENTS_FOLDER}/${project.id}/` const leftArea = ( { pendingScopeChange && @@ -221,9 +197,6 @@ class SpecificationContainer extends Component { onReject={() => this.rejectScopeChange(pendingScopeChange)} onCancel={() => this.cancelScopeChange(pendingScopeChange)} onActivate={() => this.activateScopeChange(pendingScopeChange)} - isManager={isManager} - isCustomer={isCustomer} - isAdmin={isSuperUser} isRequestor={pendingScopeChange.createdBy === currentUserId} /> } @@ -235,7 +208,6 @@ class SpecificationContainer extends Component { SpecificationContainer.propTypes = { project: PropTypes.object.isRequired, - currentMemberRole: PropTypes.string, processing: PropTypes.bool, productTemplates: PropTypes.array.isRequired, allProductTemplates: PropTypes.array.isRequired, diff --git a/src/projects/detail/containers/TeamManagementContainer.jsx b/src/projects/detail/containers/TeamManagementContainer.jsx index c77dc78f2..ef1ff83ab 100644 --- a/src/projects/detail/containers/TeamManagementContainer.jsx +++ b/src/projects/detail/containers/TeamManagementContainer.jsx @@ -4,24 +4,7 @@ import {connect} from 'react-redux' import {withRouter} from 'react-router-dom' import _ from 'lodash' import { - PROJECT_ROLE_COPILOT, - PROJECT_ROLE_CUSTOMER, PROJECT_ROLE_MANAGER, - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, - ROLE_CONNECT_COPILOT, - ROLE_CONNECT_COPILOT_MANAGER, - ROLE_CONNECT_MANAGER, - ROLE_CONNECT_ACCOUNT_MANAGER, - PROJECT_ROLE_ACCOUNT_MANAGER, - ROLE_BUSINESS_DEVELOPMENT_REPRESENTATIVE, - ROLE_PRESALES, - ROLE_ACCOUNT_EXECUTIVE, - ROLE_PROGRAM_MANAGER, - ROLE_PROJECT_MANAGER, - ROLE_SOLUTION_ARCHITECT, - PROJECT_ROLE_PROJECT_MANAGER, - PROJECT_ROLE_PROGRAM_MANAGER, PROJECT_ROLE_SOLUTION_ARCHITECT, PROJECT_ROLE_ACCOUNT_EXECUTIVE } from '../../../config/constants' import TeamManagement from '../../../components/TeamManagement/TeamManagement' import { @@ -87,11 +70,15 @@ class TeamManagementContainer extends Component { this.setState({isUserLeaving: isLeaving}) } - onJoinConfirm(role) { + /** + * Member joins themself. + * + * Currently only used to by managers to join Topcoder Team. + * + * @param {string} role role to join with + */ + onJoinConfirm(role = PROJECT_ROLE_MANAGER) { const { currentUser, projectId, addProjectMember } = this.props - let defaultRole = PROJECT_ROLE_MANAGER - if (currentUser.isCopilot) defaultRole = PROJECT_ROLE_COPILOT - role = role || defaultRole addProjectMember( projectId, {userId: currentUser.userId, role} @@ -129,31 +116,6 @@ class TeamManagementContainer extends Component { this.props.updateProjectMember(this.props.projectId, memberId, item) } - anontateMemberProps() { - const {members} = this.props - // fill project members from state.members object - return _.map(members, m => { - if (!m.userId && !m.role) return m - // map role - if (m.role === PROJECT_ROLE_COPILOT) { - m.isCopilot = true - } else if (m.role === PROJECT_ROLE_CUSTOMER) { - m.isCustomer = true - m.isPrimary = m.isPrimary || false - } else if ([ - PROJECT_ROLE_MANAGER, - PROJECT_ROLE_ACCOUNT_MANAGER, - PROJECT_ROLE_ACCOUNT_EXECUTIVE, - PROJECT_ROLE_PROJECT_MANAGER, - PROJECT_ROLE_PROGRAM_MANAGER, - PROJECT_ROLE_SOLUTION_ARCHITECT - ].includes(m.role)) { - m.isManager = true - } - return m - }) - } - onSelectedMembersUpdate(selectedMembers) { this.setState({selectedMembers}) } @@ -186,7 +148,6 @@ class TeamManagementContainer extends Component { render() { - const projectMembers = this.anontateMemberProps() const {projectTeamInvites, topcoderTeamInvites, copilotTeamInvites } = this.props return (
@@ -199,7 +160,7 @@ class TeamManagementContainer extends Component { processingInvites={this.props.processingInvites} error={this.props.error} currentUser={this.props.currentUser} - members={projectMembers} + members={this.props.members} allMembers={this.props.allMembers} projectTeamInvites={projectTeamInvites} topcoderTeamInvites={topcoderTeamInvites} @@ -223,31 +184,19 @@ class TeamManagementContainer extends Component { } } -const mapStateToProps = ({loadUser, members, projectState}) => { - const adminRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - const powerUserRoles = [ROLE_CONNECT_COPILOT, ROLE_CONNECT_MANAGER, ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - const managerRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN, ROLE_CONNECT_MANAGER, ROLE_PROGRAM_MANAGER, ROLE_PROJECT_MANAGER, ROLE_SOLUTION_ARCHITECT] - const accountManagerRoles = [ROLE_CONNECT_ACCOUNT_MANAGER, ROLE_BUSINESS_DEVELOPMENT_REPRESENTATIVE, ROLE_PRESALES, ROLE_ACCOUNT_EXECUTIVE] - return { - currentUser: { - userId: parseInt(loadUser.user.id), - isCopilot: _.indexOf(loadUser.user.roles, ROLE_CONNECT_COPILOT) > -1, - isAdmin: _.intersection(loadUser.user.roles, adminRoles).length > 0, - isManager: loadUser.user.roles.some((role) => managerRoles.indexOf(role) !== -1), - isCustomer: !loadUser.user.roles.some((role) => powerUserRoles.indexOf(role) !== -1), - isCopilotManager: _.indexOf(loadUser.user.roles, ROLE_CONNECT_COPILOT_MANAGER) > -1, - isAccountManager: loadUser.user.roles.some((role) => accountManagerRoles.indexOf(role) !== -1), - }, - allMembers: _.values(members.members), - processingInvites: projectState.processingInvites, - processingMembers: projectState.processingMembers, - updatingMemberIds: projectState.updatingMemberIds, - error: projectState.error, - topcoderTeamInvites: _.filter(projectState.project.invites, i => i.role !== 'customer' && i.role !== 'copilot'), - copilotTeamInvites: _.filter(projectState.project.invites, i => i.role === 'copilot'), - projectTeamInvites: _.filter(projectState.project.invites, i => i.role === 'customer') - } -} +const mapStateToProps = ({loadUser, members, projectState}) => ({ + currentUser: { + userId: parseInt(loadUser.user.id), + }, + allMembers: _.values(members.members), + processingInvites: projectState.processingInvites, + processingMembers: projectState.processingMembers, + updatingMemberIds: projectState.updatingMemberIds, + error: projectState.error, + topcoderTeamInvites: _.filter(projectState.project.invites, i => i.role !== 'customer' && i.role !== 'copilot'), + copilotTeamInvites: _.filter(projectState.project.invites, i => i.role === 'copilot'), + projectTeamInvites: _.filter(projectState.project.invites, i => i.role === 'customer') +}) const mapDispatchToProps = { addProjectMember, @@ -266,9 +215,6 @@ TeamManagementContainer.propTypes = { members: PropTypes.arrayOf(PropTypes.object).isRequired, currentUser: PropTypes.shape({ userId: PropTypes.number.isRequired, - isManager: PropTypes.bool, - isCustomer: PropTypes.bool, - isCopilot: PropTypes.bool }).isRequired, allMembers: PropTypes.arrayOf(PropTypes.object).isRequired, projectId: PropTypes.number.isRequired, diff --git a/src/projects/list/components/Projects/ProjectListNavHeader.jsx b/src/projects/list/components/Projects/ProjectListNavHeader.jsx index dc3531389..0192ead27 100644 --- a/src/projects/list/components/Projects/ProjectListNavHeader.jsx +++ b/src/projects/list/components/Projects/ProjectListNavHeader.jsx @@ -12,7 +12,7 @@ import CardView from '../../../../assets/icons/ui-16px-2_grid-45-gray.svg' import GridView from '../../../../assets/icons/grid-list-ico.svg' import { SwitchButton } from 'appirio-tech-react-components' -import PERMISSIONS from '../../../../config/permissions' +import { PERMISSIONS } from '../../../../config/permissions' import { hasPermission } from '../../../../helpers/permissions' export default class ProjectListNavHeader extends Component { @@ -89,7 +89,7 @@ export default class ProjectListNavHeader extends Component { ) )} - {hasPermission(PERMISSIONS.SEE_MY_PROJECTS_FILTER) && + {hasPermission(PERMISSIONS.SEE_MY_PROJECTS_FILTER) &&
}
- +
{hasPermission(PERMISSIONS.SEE_MY_PROJECTS_FILTER) && (
@@ -138,5 +138,4 @@ ProjectListNavHeader.propTypes = { history: PT.object.isRequired, setInfiniteAutoload: PT.func.isRequired, loadProjects: PT.func.isRequired, - isCustomer: PT.bool.isRequired } diff --git a/src/projects/list/components/Projects/Projects.jsx b/src/projects/list/components/Projects/Projects.jsx index e37f9f50d..198225f1f 100755 --- a/src/projects/list/components/Projects/Projects.jsx +++ b/src/projects/list/components/Projects/Projects.jsx @@ -17,13 +17,14 @@ import _ from 'lodash' import querystring from 'query-string' import { updateProject } from '../../../actions/project' import { getNewProjectLink } from '../../../../helpers/projectHelper' -import { ROLE_CONNECT_MANAGER, ROLE_CONNECT_ACCOUNT_MANAGER, ROLE_CONNECT_COPILOT, ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, PROJECT_STATUS, PROJECT_STATUS_CANCELLED, +import { PROJECT_STATUS, PROJECT_STATUS_CANCELLED, PROJECT_LIST_DEFAULT_CRITERIA, PROJECTS_LIST_VIEW, PROJECTS_LIST_PER_PAGE, SCREEN_BREAKPOINT_MD, PROJECT_MEMBER_INVITE_STATUS_ACCEPTED, PROJECT_MEMBER_INVITE_STATUS_REFUSED, } from '../../../../config/constants' import TwoColsLayout from '../../../../components/TwoColsLayout' import UserSidebar from '../../../../components/UserSidebar/UserSidebar' +import { hasPermission } from '../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../config/permissions' const page500 = compose( withProps({code:500}) @@ -235,12 +236,12 @@ class Projects extends Component { } render() { - const { isPowerUser, isCustomer, isLoading, totalCount, criteria, projectsListView, setProjectsListView, + const { isLoading, totalCount, criteria, projectsListView, setProjectsListView, setInfiniteAutoload, loadProjects, history, orgConfig, allProjectsCount, user } = this.props const { isAcceptingInvite } = this.state // show walk through if user is customer and no projects were returned // for default filters - const showWalkThrough = !isLoading && !isPowerUser && totalCount === 0 && allProjectsCount === 0 && + const showWalkThrough = !isLoading && hasPermission(PERMISSIONS.SEE_WALK_THROUGH) && totalCount === 0 && allProjectsCount === 0 && _.isEqual(criteria, PROJECT_LIST_DEFAULT_CRITERIA) const getStatusCriteriaText = (criteria) => { return (_.find(PROJECT_STATUS, { value: criteria.status }) || { name: ''}).name @@ -255,7 +256,6 @@ class Projects extends Component { newProjectLink={getNewProjectLink(orgConfig)} setFilter={this.setFilter} criteria={criteria} - isCustomer={isCustomer} callInviteRequest={this.callInviteRequest} isAcceptingInvite={isAcceptingInvite} /> @@ -315,7 +315,6 @@ class Projects extends Component { setInfiniteAutoload={setInfiniteAutoload} loadProjects={loadProjects} history={history} - isCustomer={isCustomer} /> )} {showWalkThrough ? ( @@ -333,16 +332,11 @@ class Projects extends Component { } const mapStateToProps = ({ projectSearch, members, loadUser, projectState, templates, notifications }) => { - let isPowerUser = false - const roles = [ROLE_CONNECT_COPILOT, ROLE_CONNECT_MANAGER, ROLE_CONNECT_ACCOUNT_MANAGER, ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - if (loadUser.user) { - isPowerUser = loadUser.user.roles.some((role) => roles.indexOf(role) !== -1) - } if (projectState.project && projectState.project.id && projectSearch.projects) { const index = _.findIndex(projectSearch.projects, {id: projectState.project.id}) projectSearch.projects.splice(index, 1, projectState.project) } - const defaultListView = isPowerUser ? PROJECTS_LIST_VIEW.GRID : PROJECTS_LIST_VIEW.CARD + const defaultListView = hasPermission(PERMISSIONS.SEE_GRID_VIEW_BY_DEFAULT) ? PROJECTS_LIST_VIEW.GRID : PROJECTS_LIST_VIEW.CARD return { currentUser : { userId: loadUser.user.userId, @@ -363,8 +357,6 @@ const mapStateToProps = ({ projectSearch, members, loadUser, projectState, templ criteria : projectSearch.criteria, infiniteAutoload: projectSearch.infiniteAutoload, projectsListView: projectSearch.projectsListView || defaultListView, - isPowerUser, - isCustomer : !isPowerUser, refresh : projectSearch.refresh, projectTemplates: templates.projectTemplates, isProjectTemplatesLoading: templates.isLoading, diff --git a/src/projects/list/components/Projects/ProjectsGridView.jsx b/src/projects/list/components/Projects/ProjectsGridView.jsx index 18d22978a..8c295704e 100644 --- a/src/projects/list/components/Projects/ProjectsGridView.jsx +++ b/src/projects/list/components/Projects/ProjectsGridView.jsx @@ -9,7 +9,6 @@ import { filterTopicAndPostChangedNotifications, filterFileAndLinkChangedNotifications, } from '../../../../routes/notifications/helpers/notifications' -import { getProjectRoleForCurrentUser } from '../../../../helpers/projectHelper' import ProjectListTimeSortColHeader from './ProjectListTimeSortColHeader' import ProjectListFilterColHeader from './ProjectListFilterColHeader' import GridView from '../../../../components/Grid/GridView' @@ -17,9 +16,6 @@ import Invitation from '../../../../components/Invitation/Invitation' import { PROJECTS_LIST_PER_PAGE, SORT_OPTIONS, PROJECT_STATUS_COMPLETED, DATE_TO_USER_FIELD_MAP, PHASE_STATUS_REVIEWED, PHASE_STATUS_ACTIVE, PROJECT_STATUS_ACTIVE, TOOLTIP_DEFAULT_DELAY, - ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN, - PROJECT_ROLE_COPILOT, PROJECT_ROLE_MANAGER, PROJECT_ROLE_PROGRAM_MANAGER, - PROJECT_ROLE_PROJECT_MANAGER, PROJECT_ROLE_SOLUTION_ARCHITECT, } from '../../../../config/constants' import { getProjectTemplateByKey } from '../../../../helpers/templates' import TextTruncate from 'react-text-truncate' @@ -33,6 +29,8 @@ import Tooltip from 'appirio-tech-react-components/components/Tooltip/Tooltip' import './ProjectsGridView.scss' import NotificationBadge from '../../../../components/NotificationBadge/NotificationBadge' import { getFullNameWithFallback } from '../../../../helpers/tcHelpers' +import { hasPermission } from '../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../config/permissions' const EnhancedProjectStatus = editableProjectStatus(ProjectStatus) @@ -227,16 +225,7 @@ const ProjectsGridView = props => { sortable: false, classes: 'item-status', renderText: item => { - const isSuperUser = _.intersection([ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN], currentUser.roles).length > 0 - const userProjectRole = getProjectRoleForCurrentUser({ project: item, currentUserId: currentUser.userId }) - const statusEditRoles = [ - PROJECT_ROLE_COPILOT, - PROJECT_ROLE_MANAGER, - PROJECT_ROLE_PROGRAM_MANAGER, - PROJECT_ROLE_PROJECT_MANAGER, - PROJECT_ROLE_SOLUTION_ARCHITECT - ] - const canEdit = item.status !== PROJECT_STATUS_COMPLETED && (isSuperUser || _.includes(statusEditRoles, userProjectRole)) + const canEdit = item.status !== PROJECT_STATUS_COMPLETED && hasPermission(PERMISSIONS.EDIT_PROJECT_STATUS, { project: item }) const hasReviewedOrActivePhases = !!_.find(item.phases, (phase) => _.includes([PHASE_STATUS_REVIEWED, PHASE_STATUS_ACTIVE], phase.status)) const isProjectActive = item.status === PROJECT_STATUS_ACTIVE @@ -320,7 +309,6 @@ ProjectsGridView.propTypes = { criteria: PropTypes.object.isRequired, projectTemplates: PropTypes.array.isRequired, setFilter: PropTypes.func, - isCustomer: PropTypes.bool.isRequired, callInviteRequest: PropTypes.func, isAcceptingInvite: PropTypes.object, } diff --git a/src/routes/metadata/components/MetaDataPanel.jsx b/src/routes/metadata/components/MetaDataPanel.jsx index b4e6fce1e..c1cf969ea 100644 --- a/src/routes/metadata/components/MetaDataPanel.jsx +++ b/src/routes/metadata/components/MetaDataPanel.jsx @@ -22,6 +22,8 @@ import XMarkIcon from '../../../assets/icons/x-mark.svg' import './MetaDataPanel.scss' import FullScreenJSONEditor from './FullScreenJSONEditor' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const phasesDefaultValue = { '1-dev-iteration-i': { @@ -818,7 +820,7 @@ class MetaDataPanel extends React.Component { } render() { - const { isAdmin, metadataType, templates } = this.props + const { metadataType, templates } = this.props const { fields, metadata, isNew, metadataWithVersion, isFullScreen } = this.state let template = {} if (metadata && metadataType === 'projectTemplate' && metadata.scope) { @@ -828,8 +830,7 @@ class MetaDataPanel extends React.Component { } else if (metadata && metadataWithVersion && metadata.config) { template = metadata.config } - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -920,7 +921,6 @@ MetaDataPanel.propTypes = { createProjectsMetadata: PropTypes.func.isRequired, updateProjectsMetadata: PropTypes.func.isRequired, templates: PropTypes.object.isRequired, - isAdmin: PropTypes.bool.isRequired, previewProject: PropTypes.object, routerParams: PropTypes.object, firePreviewProjectDirty: PropTypes.func, diff --git a/src/routes/metadata/containers/FormDetails.jsx b/src/routes/metadata/containers/FormDetails.jsx index daa1f15e7..565bfc0b3 100644 --- a/src/routes/metadata/containers/FormDetails.jsx +++ b/src/routes/metadata/containers/FormDetails.jsx @@ -20,10 +20,6 @@ import LoadingIndicator from '../../../components/LoadingIndicator/LoadingIndica import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import MetaDataPanel from '../components/MetaDataPanel' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -67,7 +63,6 @@ class FormDetails extends React.Component { updateProjectsMetadata, getRevisionList, templates, - isAdmin, match, isLoading } = this.props @@ -82,7 +77,6 @@ class FormDetails extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - isLoading: templates.isLoading, - versionOptionsLoading: templates.versionOptionsLoading, - isRemoving: templates.isRemoving, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates, + isLoading: templates.isLoading, + versionOptionsLoading: templates.versionOptionsLoading, + isRemoving: templates.isRemoving, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, @@ -154,4 +142,4 @@ const enhance = spinnerWhileLoading( const FormDetailsWithLoaderEnhanced = enhance(errorHandler(FormDetails)) const FormDetailsWithLoaderAndAuth = requiresAuthentication(FormDetailsWithLoaderEnhanced) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(FormDetailsWithLoaderAndAuth)) \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(FormDetailsWithLoaderAndAuth)) diff --git a/src/routes/metadata/containers/FormsContainer.jsx b/src/routes/metadata/containers/FormsContainer.jsx index e129501c9..373b5c180 100644 --- a/src/routes/metadata/containers/FormsContainer.jsx +++ b/src/routes/metadata/containers/FormsContainer.jsx @@ -14,14 +14,11 @@ import FormsGridView from '../components/FormsGridView' import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' -import _ from 'lodash' import CoderBroken from '../../../assets/icons/coder-broken.svg' import './MetaDataContainer.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const withLoader = spinnerWhileLoading(props => !props.isLoading && props.forms) const FormsGridViewWithLoader = withLoader(FormsGridView) @@ -50,13 +47,11 @@ class FormsContainer extends React.Component { const { forms, isLoading, - isAdmin, currentUser, error, } = this.props const { criteria } = this.state - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -87,21 +82,15 @@ class FormsContainer extends React.Component { } FormsContainer.propTypes = { - isAdmin: PropTypes.bool.isRequired, loadProjectsMetadata: PropTypes.func.isRequired, } -const mapStateToProps = ({ templates, loadUser }) => { - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - forms: templates.forms, - isLoading: templates.isLoading, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + forms: templates.forms, + isLoading: templates.isLoading, + error: templates.error, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/MetaDataContainer.jsx b/src/routes/metadata/containers/MetaDataContainer.jsx index 3255a7681..b62b3d78a 100644 --- a/src/routes/metadata/containers/MetaDataContainer.jsx +++ b/src/routes/metadata/containers/MetaDataContainer.jsx @@ -25,10 +25,6 @@ import MetaDataMilestoneTemplatesGridView from '../components/MetaDataMilestoneT import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -52,7 +48,6 @@ class MetaDataContainer extends React.Component { createProjectsMetadata, updateProjectsMetadata, templates, - isAdmin, currentUser, metadataType, match, @@ -82,7 +77,6 @@ class MetaDataContainer extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/MilestoneTemplateDetails.jsx b/src/routes/metadata/containers/MilestoneTemplateDetails.jsx index 96d9d6f18..c4b62cb93 100644 --- a/src/routes/metadata/containers/MilestoneTemplateDetails.jsx +++ b/src/routes/metadata/containers/MilestoneTemplateDetails.jsx @@ -17,10 +17,6 @@ import LoadingIndicator from '../../../components/LoadingIndicator/LoadingIndica import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import MetaDataPanel from '../components/MetaDataPanel' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -45,7 +41,6 @@ class MilestoneTemplateDetails extends React.Component { updateProjectsMetadata, templates, isLoading, - isAdmin, match, } = this.props const milestoneTemplates = templates.milestoneTemplates @@ -57,7 +52,6 @@ class MilestoneTemplateDetails extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - isLoading: templates.isLoading, - isRemoving: templates.isRemoving, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates, + isLoading: templates.isLoading, + isRemoving: templates.isRemoving, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/MilestoneTemplatesContainer.jsx b/src/routes/metadata/containers/MilestoneTemplatesContainer.jsx index 1f8b904dc..6a71e7d6b 100644 --- a/src/routes/metadata/containers/MilestoneTemplatesContainer.jsx +++ b/src/routes/metadata/containers/MilestoneTemplatesContainer.jsx @@ -14,14 +14,11 @@ import MilestoneTemplatesGridView from '../components/MilestoneTemplatesGridView import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' -import _ from 'lodash' import CoderBroken from '../../../assets/icons/coder-broken.svg' import './MetaDataContainer.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const withLoader = spinnerWhileLoading(props => !props.isLoading && props.milestoneTemplates) const MilestoneTemplatesGridViewWithLoader = withLoader(MilestoneTemplatesGridView) @@ -51,14 +48,12 @@ class MilestoneTemplatesContainer extends React.Component { milestoneTemplates, productTemplates, isLoading, - isAdmin, currentUser, error, } = this.props const { criteria } = this.state - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -92,23 +87,17 @@ class MilestoneTemplatesContainer extends React.Component { MilestoneTemplatesContainer.propTypes = { - isAdmin: PropTypes.bool.isRequired, loadProjectsMetadata: PropTypes.func.isRequired, } -const mapStateToProps = ({ templates, loadUser }) => { - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - productTemplates: templates.productTemplates, - milestoneTemplates: templates.milestoneTemplates, - isLoading: templates.isLoading, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + productTemplates: templates.productTemplates, + milestoneTemplates: templates.milestoneTemplates, + isLoading: templates.isLoading, + error: templates.error, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/PlanConfigDetails.jsx b/src/routes/metadata/containers/PlanConfigDetails.jsx index 3f2bd781b..2f8fd4f93 100644 --- a/src/routes/metadata/containers/PlanConfigDetails.jsx +++ b/src/routes/metadata/containers/PlanConfigDetails.jsx @@ -20,10 +20,6 @@ import LoadingIndicator from '../../../components/LoadingIndicator/LoadingIndica import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import MetaDataPanel from '../components/MetaDataPanel' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -67,7 +63,6 @@ class PlanConfigDetails extends React.Component { updateProjectsMetadata, getRevisionList, templates, - isAdmin, match, isLoading } = this.props @@ -82,7 +77,6 @@ class PlanConfigDetails extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - isLoading: templates.isLoading, - versionOptionsLoading: templates.versionOptionsLoading, - isRemoving: templates.isRemoving, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates, + isLoading: templates.isLoading, + versionOptionsLoading: templates.versionOptionsLoading, + isRemoving: templates.isRemoving, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, @@ -154,4 +142,4 @@ const enhance = spinnerWhileLoading( const PlanConfigDetailsWithLoaderEnhanced = enhance(errorHandler(PlanConfigDetails)) const PlanConfigDetailsWithLoaderAndAuth = requiresAuthentication(PlanConfigDetailsWithLoaderEnhanced) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PlanConfigDetailsWithLoaderAndAuth)) \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PlanConfigDetailsWithLoaderAndAuth)) diff --git a/src/routes/metadata/containers/PlanConfigsContainer.jsx b/src/routes/metadata/containers/PlanConfigsContainer.jsx index 7be335419..c91a97384 100644 --- a/src/routes/metadata/containers/PlanConfigsContainer.jsx +++ b/src/routes/metadata/containers/PlanConfigsContainer.jsx @@ -14,14 +14,11 @@ import PlanConfigsGridView from '../components/PlanConfigsGridView' import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' -import _ from 'lodash' import CoderBroken from '../../../assets/icons/coder-broken.svg' import './MetaDataContainer.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const withLoader = spinnerWhileLoading(props => !props.isLoading && props.planConfigs) const PlanConfigsGridViewWithLoader = withLoader(PlanConfigsGridView) @@ -50,13 +47,11 @@ class PlanConfigsContainer extends React.Component { const { planConfigs, isLoading, - isAdmin, currentUser, error, } = this.props const { criteria } = this.state - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -87,21 +82,15 @@ class PlanConfigsContainer extends React.Component { } PlanConfigsContainer.propTypes = { - isAdmin: PropTypes.bool.isRequired, loadProjectsMetadata: PropTypes.func.isRequired, } -const mapStateToProps = ({ templates, loadUser }) => { - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - planConfigs: templates.planConfigs, - isLoading: templates.isLoading, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + planConfigs: templates.planConfigs, + isLoading: templates.isLoading, + error: templates.error, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/PriceConfigDetails.jsx b/src/routes/metadata/containers/PriceConfigDetails.jsx index b6954f965..e99c1b9b4 100644 --- a/src/routes/metadata/containers/PriceConfigDetails.jsx +++ b/src/routes/metadata/containers/PriceConfigDetails.jsx @@ -20,10 +20,6 @@ import LoadingIndicator from '../../../components/LoadingIndicator/LoadingIndica import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import MetaDataPanel from '../components/MetaDataPanel' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -67,7 +63,6 @@ class PriceConfigDetails extends React.Component { updateProjectsMetadata, getRevisionList, templates, - isAdmin, match, isLoading } = this.props @@ -82,7 +77,6 @@ class PriceConfigDetails extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - isLoading: templates.isLoading, - versionOptionsLoading: templates.versionOptionsLoading, - isRemoving: templates.isRemoving, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates, + isLoading: templates.isLoading, + versionOptionsLoading: templates.versionOptionsLoading, + isRemoving: templates.isRemoving, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, @@ -154,4 +142,4 @@ const enhance = spinnerWhileLoading( const PriceConfigDetailsWithLoaderEnhanced = enhance(errorHandler(PriceConfigDetails)) const PriceConfigDetailsWithLoaderAndAuth = requiresAuthentication(PriceConfigDetailsWithLoaderEnhanced) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PriceConfigDetailsWithLoaderAndAuth)) \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PriceConfigDetailsWithLoaderAndAuth)) diff --git a/src/routes/metadata/containers/PriceConfigsContainer.jsx b/src/routes/metadata/containers/PriceConfigsContainer.jsx index 9c430e600..705ae30f9 100644 --- a/src/routes/metadata/containers/PriceConfigsContainer.jsx +++ b/src/routes/metadata/containers/PriceConfigsContainer.jsx @@ -14,14 +14,11 @@ import PriceConfigsGridView from '../components/PriceConfigsGridView' import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' -import _ from 'lodash' import CoderBroken from '../../../assets/icons/coder-broken.svg' import './MetaDataContainer.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const withLoader = spinnerWhileLoading(props => !props.isLoading && props.priceConfigs) const PriceConfigsGridViewWithLoader = withLoader(PriceConfigsGridView) @@ -50,13 +47,11 @@ class PriceConfigsContainer extends React.Component { const { priceConfigs, isLoading, - isAdmin, currentUser, error, } = this.props const { criteria } = this.state - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -87,21 +82,15 @@ class PriceConfigsContainer extends React.Component { } PriceConfigsContainer.propTypes = { - isAdmin: PropTypes.bool.isRequired, loadProjectsMetadata: PropTypes.func.isRequired, } -const mapStateToProps = ({ templates, loadUser }) => { - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - priceConfigs: templates.priceConfigs, - isLoading: templates.isLoading, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + priceConfigs: templates.priceConfigs, + isLoading: templates.isLoading, + error: templates.error, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/ProductCategoriesContainer.jsx b/src/routes/metadata/containers/ProductCategoriesContainer.jsx index 8ff1b7b74..60bc25bf0 100644 --- a/src/routes/metadata/containers/ProductCategoriesContainer.jsx +++ b/src/routes/metadata/containers/ProductCategoriesContainer.jsx @@ -14,14 +14,11 @@ import ProductCategoriesGridView from '../components/ProductCategoriesGridView' import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' -import _ from 'lodash' import CoderBroken from '../../../assets/icons/coder-broken.svg' import './MetaDataContainer.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const withLoader = spinnerWhileLoading(props => !props.isLoading && props.productCategories) const ProductCategoriesGridViewWithLoader = withLoader(ProductCategoriesGridView) @@ -50,13 +47,11 @@ class ProductCategoriesContainer extends React.Component { const { productCategories, isLoading, - isAdmin, currentUser, error, } = this.props const { criteria } = this.state - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -89,22 +84,16 @@ class ProductCategoriesContainer extends React.Component { ProductCategoriesContainer.propTypes = { - isAdmin: PropTypes.bool.isRequired, loadProjectsMetadata: PropTypes.func.isRequired, } -const mapStateToProps = ({ templates, loadUser }) => { - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - productCategories: templates.productCategories, - isLoading: templates.isLoading, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + productCategories: templates.productCategories, + isLoading: templates.isLoading, + error: templates.error, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/ProductCategoryDetails.jsx b/src/routes/metadata/containers/ProductCategoryDetails.jsx index 7fd6d4285..46b4c9688 100644 --- a/src/routes/metadata/containers/ProductCategoryDetails.jsx +++ b/src/routes/metadata/containers/ProductCategoryDetails.jsx @@ -17,10 +17,6 @@ import LoadingIndicator from '../../../components/LoadingIndicator/LoadingIndica import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import MetaDataPanel from '../components/MetaDataPanel' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -45,7 +41,6 @@ class ProductCategoryDetails extends React.Component { updateProjectsMetadata, templates, isLoading, - isAdmin, match, } = this.props const productCategories = templates.productCategories @@ -57,7 +52,6 @@ class ProductCategoryDetails extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - isLoading: templates.isLoading, - isRemoving: templates.isRemoving, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates, + isLoading: templates.isLoading, + isRemoving: templates.isRemoving, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, @@ -118,4 +106,4 @@ const enhance = spinnerWhileLoading( const ProductCategoryDetailsWithLoaderEnhanced = enhance(errorHandler(ProductCategoryDetails)) const ProductCategoryDetailsWithLoaderAndAuth = requiresAuthentication(ProductCategoryDetailsWithLoaderEnhanced) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ProductCategoryDetailsWithLoaderAndAuth)) \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ProductCategoryDetailsWithLoaderAndAuth)) diff --git a/src/routes/metadata/containers/ProductTemplateDetails.jsx b/src/routes/metadata/containers/ProductTemplateDetails.jsx index fe72b0cf4..b9443bb2a 100644 --- a/src/routes/metadata/containers/ProductTemplateDetails.jsx +++ b/src/routes/metadata/containers/ProductTemplateDetails.jsx @@ -17,10 +17,6 @@ import LoadingIndicator from '../../../components/LoadingIndicator/LoadingIndica import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import MetaDataPanel from '../components/MetaDataPanel' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -45,7 +41,6 @@ class ProductTemplateDetails extends React.Component { updateProjectsMetadata, templates, isLoading, - isAdmin, match, } = this.props const productTemplates = templates.productTemplates @@ -58,7 +53,6 @@ class ProductTemplateDetails extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - isLoading: templates.isLoading, - isRemoving: templates.isRemoving, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates, + isLoading: templates.isLoading, + isRemoving: templates.isRemoving, + error: templates.error, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, @@ -120,4 +108,4 @@ const enhance = spinnerWhileLoading( const ProductTemplateDetailsWithLoaderEnhanced = enhance(errorHandler(ProductTemplateDetails)) const ProductTemplateDetailsWithLoaderAndAuth = requiresAuthentication(ProductTemplateDetailsWithLoaderEnhanced) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ProductTemplateDetailsWithLoaderAndAuth)) \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ProductTemplateDetailsWithLoaderAndAuth)) diff --git a/src/routes/metadata/containers/ProductTemplatesContainer.jsx b/src/routes/metadata/containers/ProductTemplatesContainer.jsx index e6c1ec907..8d0f58f29 100644 --- a/src/routes/metadata/containers/ProductTemplatesContainer.jsx +++ b/src/routes/metadata/containers/ProductTemplatesContainer.jsx @@ -14,14 +14,11 @@ import ProductTemplatesGridView from '../components/ProductTemplatesGridView' import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' -import _ from 'lodash' import CoderBroken from '../../../assets/icons/coder-broken.svg' import './MetaDataContainer.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const withLoader = spinnerWhileLoading(props => !props.isLoading && props.productTemplates) const ProductTemplatesGridViewWithLoader = withLoader(ProductTemplatesGridView) @@ -49,13 +46,11 @@ class ProductTemplatesContainer extends React.Component { const { templates, isLoading, - isAdmin, currentUser, error, } = this.props const { criteria } = this.state - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -88,23 +83,17 @@ class ProductTemplatesContainer extends React.Component { ProductTemplatesContainer.propTypes = { - isAdmin: PropTypes.bool.isRequired, loadProjectsMetadata: PropTypes.func.isRequired, sortProductTemplates: PropTypes.func.isRequired, } -const mapStateToProps = ({ templates, loadUser }) => { - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates: templates.productTemplates, - isLoading: templates.isLoading, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0, - error: templates.error, - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates: templates.productTemplates, + isLoading: templates.isLoading, + currentUser: loadUser.user, + error: templates.error, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/ProjectTemplateDetails.jsx b/src/routes/metadata/containers/ProjectTemplateDetails.jsx index cacc4820c..55e486881 100644 --- a/src/routes/metadata/containers/ProjectTemplateDetails.jsx +++ b/src/routes/metadata/containers/ProjectTemplateDetails.jsx @@ -18,10 +18,6 @@ import LoadingIndicator from '../../../components/LoadingIndicator/LoadingIndica import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import MetaDataPanel from '../components/MetaDataPanel' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -46,7 +42,6 @@ class ProjectTemplateDetails extends React.Component { updateProjectsMetadata, templates, isLoading, - isAdmin, match, previewProject, firePreviewProjectDirty, @@ -61,7 +56,6 @@ class ProjectTemplateDetails extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - isLoading: templates.isLoading, - isRemoving: templates.isRemoving, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0, - previewProject: projectState.project, - } -} +const mapStateToProps = ({ projectState, templates, loadUser }) => ({ + templates, + isLoading: templates.isLoading, + isRemoving: templates.isRemoving, + error: templates.error, + currentUser: loadUser.user, + previewProject: projectState.project, +}) const mapDispatchToProps = { loadProjectsMetadata, @@ -129,4 +117,4 @@ const enhance = spinnerWhileLoading( const ProjectTemplateDetailsWithLoaderEnhanced = enhance(errorHandler(ProjectTemplateDetails)) const ProjectTemplateDetailsWithLoaderAndAuth = requiresAuthentication(ProjectTemplateDetailsWithLoaderEnhanced) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ProjectTemplateDetailsWithLoaderAndAuth)) \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ProjectTemplateDetailsWithLoaderAndAuth)) diff --git a/src/routes/metadata/containers/ProjectTemplatesContainer.jsx b/src/routes/metadata/containers/ProjectTemplatesContainer.jsx index 5b8d0f9ad..415cc6545 100644 --- a/src/routes/metadata/containers/ProjectTemplatesContainer.jsx +++ b/src/routes/metadata/containers/ProjectTemplatesContainer.jsx @@ -14,14 +14,11 @@ import ProjectTemplatesGridView from '../components/ProjectTemplatesGridView' import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' -import _ from 'lodash' import CoderBroken from '../../../assets/icons/coder-broken.svg' import './MetaDataContainer.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const withLoader = spinnerWhileLoading(props => !props.isLoading && props.projectTemplates) const ProjectTemplatesGridViewWithLoader = withLoader(ProjectTemplatesGridView) @@ -50,13 +47,11 @@ class ProjectTemplatesContainer extends React.Component { const { templates, isLoading, - isAdmin, currentUser, error, } = this.props const { criteria } = this.state - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -90,23 +85,17 @@ class ProjectTemplatesContainer extends React.Component { ProjectTemplatesContainer.propTypes = { - isAdmin: PropTypes.bool.isRequired, loadProjectsMetadata: PropTypes.func.isRequired, sortProjectTemplates: PropTypes.func.isRequired, } -const mapStateToProps = ({ templates, loadUser }) => { - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates: templates.projectTemplates, - isLoading: templates.isLoading, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates: templates.projectTemplates, + isLoading: templates.isLoading, + error: templates.error, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/metadata/containers/ProjectTypeDetails.jsx b/src/routes/metadata/containers/ProjectTypeDetails.jsx index 3a5c7bf1f..01f08f1a8 100644 --- a/src/routes/metadata/containers/ProjectTypeDetails.jsx +++ b/src/routes/metadata/containers/ProjectTypeDetails.jsx @@ -17,10 +17,6 @@ import LoadingIndicator from '../../../components/LoadingIndicator/LoadingIndica import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' import MetaDataPanel from '../components/MetaDataPanel' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' import _ from 'lodash' import './MetaDataContainer.scss' @@ -45,7 +41,6 @@ class ProjectTypeDetails extends React.Component { updateProjectsMetadata, templates, isLoading, - isAdmin, match, } = this.props const projectTypes = templates.projectTypes @@ -57,7 +52,6 @@ class ProjectTypeDetails extends React.Component {
{ - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - templates, - isLoading: templates.isLoading, - isRemoving: templates.isRemoving, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + templates, + isLoading: templates.isLoading, + isRemoving: templates.isRemoving, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, @@ -118,4 +106,4 @@ const enhance = spinnerWhileLoading( const ProjectTypeDetailsWithLoaderEnhanced = enhance(errorHandler(ProjectTypeDetails)) const ProjectTypeDetailsWithLoaderAndAuth = requiresAuthentication(ProjectTypeDetailsWithLoaderEnhanced) -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ProjectTypeDetailsWithLoaderAndAuth)) \ No newline at end of file +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ProjectTypeDetailsWithLoaderAndAuth)) diff --git a/src/routes/metadata/containers/ProjectTypesContainer.jsx b/src/routes/metadata/containers/ProjectTypesContainer.jsx index 2e573686f..fcde59a46 100644 --- a/src/routes/metadata/containers/ProjectTypesContainer.jsx +++ b/src/routes/metadata/containers/ProjectTypesContainer.jsx @@ -14,14 +14,11 @@ import ProjectTypesGridView from '../components/ProjectTypesGridView' import spinnerWhileLoading from '../../../components/LoadingSpinner' import CoderBot from '../../../components/CoderBot/CoderBot' import { requiresAuthentication } from '../../../components/AuthenticatedComponent' -import { - ROLE_ADMINISTRATOR, - ROLE_CONNECT_ADMIN, -} from '../../../config/constants' -import _ from 'lodash' import CoderBroken from '../../../assets/icons/coder-broken.svg' import './MetaDataContainer.scss' +import { hasPermission } from '../../../helpers/permissions' +import { PERMISSIONS } from '../../../config/permissions' const withLoader = spinnerWhileLoading(props => !props.isLoading && props.projectTypes) const ProjectTypesGridViewWithLoader = withLoader(ProjectTypesGridView) @@ -50,13 +47,11 @@ class ProjectTypesContainer extends React.Component { const { projectTypes, isLoading, - isAdmin, currentUser, error, } = this.props const { criteria } = this.state - // TODO remove: temporary let non-admin user see metadata (they still couldn't save because server will reject) - if (!isAdmin && isAdmin) { + if (!hasPermission(PERMISSIONS.ACCESS_METADATA)) { return (
@@ -89,22 +84,16 @@ class ProjectTypesContainer extends React.Component { ProjectTypesContainer.propTypes = { - isAdmin: PropTypes.bool.isRequired, loadProjectsMetadata: PropTypes.func.isRequired, } -const mapStateToProps = ({ templates, loadUser }) => { - const powerUserRoles = [ROLE_ADMINISTRATOR, ROLE_CONNECT_ADMIN] - - return { - projectTypes: templates.projectTypes, - isLoading: templates.isLoading, - error: templates.error, - currentUser: loadUser.user, - isAdmin: _.intersection(loadUser.user.roles, powerUserRoles).length !== 0 - } -} +const mapStateToProps = ({ templates, loadUser }) => ({ + projectTypes: templates.projectTypes, + isLoading: templates.isLoading, + error: templates.error, + currentUser: loadUser.user, +}) const mapDispatchToProps = { loadProjectsMetadata, diff --git a/src/routes/settings/routes/notifications/components/NotificationSettingsForm.jsx b/src/routes/settings/routes/notifications/components/NotificationSettingsForm.jsx index 934212fb8..581a72ec7 100644 --- a/src/routes/settings/routes/notifications/components/NotificationSettingsForm.jsx +++ b/src/routes/settings/routes/notifications/components/NotificationSettingsForm.jsx @@ -14,6 +14,8 @@ import './NotificationSettingsForm.scss' import _ from 'lodash' import SelectDropdown from '../../../../../components/SelectDropdown/SelectDropdown' import SwitchButton from 'appirio-tech-react-components/components/SwitchButton/SwitchButton' +import { hasPermission } from '../../../../../helpers/permissions' +import { PERMISSIONS } from '../../../../../config/permissions' // list of the notification groups and related event types @@ -256,7 +258,6 @@ class NotificationSettingsForm extends React.Component { } render() { - const { isCustomer } = this.props const areSettingsProvided = !!this.props.values.settings const settings = this.state.settings const notifications = settings.notifications @@ -266,6 +267,8 @@ class NotificationSettingsForm extends React.Component { return null } + const canToggleWebsiteNotifications = hasPermission(PERMISSIONS.TOGGLE_WEBSITE_NOTIFICATIONS) + return ( - {isCustomer ? ( + {!canToggleWebsiteNotifications ? (