From 5f7784e32941dcc2b3c549b74bdc084c2d6b4b2c Mon Sep 17 00:00:00 2001 From: Pascal Mugnier Date: Thu, 20 Sep 2018 10:27:25 +0200 Subject: [PATCH] SONAR-8019 Ease the workflow for adding permissions to new users/groups --- .../sonar-web/src/main/js/api/permissions.ts | 205 +++++------- server/sonar-web/src/main/js/app/types.ts | 22 ++ .../global/components/AllHoldersList.tsx | 61 ++-- .../components/AllHoldersListContainer.tsx | 77 ----- .../permissions/global/components/App.tsx | 310 +++++++++++++++++- .../{PageHeader.js => PageHeader.tsx} | 25 +- .../apps/permissions/global/store/actions.js | 179 ---------- .../{AllHoldersList.js => AllHoldersList.tsx} | 122 +++---- .../project/components/{App.js => App.tsx} | 255 +++++++------- .../{AppContainer.js => AppContainer.ts} | 4 +- .../project/components/PageHeader.tsx | 4 +- .../project/{constants.js => constants.tsx} | 2 +- .../shared/components/GroupHolder.tsx | 2 +- .../shared/components/HoldersList.tsx | 64 +++- .../shared/components/PageError.tsx | 40 --- .../shared/components/PermissionHeader.tsx | 7 +- .../shared/components/UserHolder.tsx | 2 +- .../components/__tests__/HoldersList-test.tsx | 6 +- .../__snapshots__/HoldersList-test.tsx.snap | 76 ++++- .../apps/permissions/shared/store/actions.js | 34 -- .../js/apps/permissions/shared/store/error.js | 44 --- .../apps/permissions/shared/store/filter.js | 29 -- .../permissions/shared/store/groups/byName.js | 48 --- .../permissions/shared/store/groups/groups.js | 30 -- .../permissions/shared/store/groups/names.js | 32 -- .../apps/permissions/shared/store/loading.js | 35 -- .../js/apps/permissions/shared/store/query.js | 32 -- .../permissions/shared/store/rootReducer.js | 51 --- .../shared/store/selectedPermission.js | 29 -- .../permissions/shared/store/users/byLogin.js | 48 --- .../permissions/shared/store/users/logins.js | 32 -- .../permissions/shared/store/users/users.js | 30 -- .../src/main/js/apps/permissions/styles.css | 11 +- .../projectsManagement/RestoreAccessModal.tsx | 10 +- .../src/main/js/store/rootReducer.ts | 31 -- 35 files changed, 775 insertions(+), 1214 deletions(-) delete mode 100644 server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersListContainer.tsx rename server/sonar-web/src/main/js/apps/permissions/global/components/{PageHeader.js => PageHeader.tsx} (76%) delete mode 100644 server/sonar-web/src/main/js/apps/permissions/global/store/actions.js rename server/sonar-web/src/main/js/apps/permissions/project/components/{AllHoldersList.js => AllHoldersList.tsx} (52%) rename server/sonar-web/src/main/js/apps/permissions/project/components/{App.js => App.tsx} (60%) rename server/sonar-web/src/main/js/apps/permissions/project/components/{AppContainer.js => AppContainer.ts} (89%) rename server/sonar-web/src/main/js/apps/permissions/project/{constants.js => constants.tsx} (94%) delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.tsx delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/actions.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/error.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/filter.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/groups/byName.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/groups/groups.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/groups/names.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/loading.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/query.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/rootReducer.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/selectedPermission.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/users/byLogin.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/users/logins.js delete mode 100644 server/sonar-web/src/main/js/apps/permissions/shared/store/users/users.js diff --git a/server/sonar-web/src/main/js/api/permissions.ts b/server/sonar-web/src/main/js/api/permissions.ts index 116e242e3c2..235d5c5ef36 100644 --- a/server/sonar-web/src/main/js/api/permissions.ts +++ b/server/sonar-web/src/main/js/api/permissions.ts @@ -18,73 +18,51 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { BaseSearchProjectsParameters } from './components'; -import { PermissionTemplate, Visibility } from '../app/types'; -import throwGlobalError from '../app/utils/throwGlobalError'; +import { + Paging, + PermissionGroup, + PermissionTemplate, + PermissionUser, + Visibility +} from '../app/types'; import { getJSON, post, postJSON, RequestData } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; const PAGE_SIZE = 100; -export function grantPermissionToUser( - projectKey: string | null, - login: string, - permission: string, - organization?: string -) { - const data: RequestData = { login, permission }; - if (projectKey) { - data.projectKey = projectKey; - } - if (organization && !projectKey) { - data.organization = organization; - } +export function grantPermissionToUser(data: { + projectKey?: string; + login: string; + permission: string; + organization?: string; +}) { return post('/api/permissions/add_user', data).catch(throwGlobalError); } -export function revokePermissionFromUser( - projectKey: string | null, - login: string, - permission: string, - organization?: string -) { - const data: RequestData = { login, permission }; - if (projectKey) { - data.projectKey = projectKey; - } - if (organization && !projectKey) { - data.organization = organization; - } +export function revokePermissionFromUser(data: { + projectKey?: string; + login: string; + permission: string; + organization?: string; +}) { return post('/api/permissions/remove_user', data).catch(throwGlobalError); } -export function grantPermissionToGroup( - projectKey: string | null, - groupName: string, - permission: string, - organization?: string -) { - const data: RequestData = { groupName, permission }; - if (projectKey) { - data.projectKey = projectKey; - } - if (organization) { - data.organization = organization; - } +export function grantPermissionToGroup(data: { + projectKey?: string; + groupName: string; + permission: string; + organization?: string; +}) { return post('/api/permissions/add_group', data).catch(throwGlobalError); } -export function revokePermissionFromGroup( - projectKey: string | null, - groupName: string, - permission: string, - organization?: string -) { - const data: RequestData = { groupName, permission }; - if (projectKey) { - data.projectKey = projectKey; - } - if (organization) { - data.organization = organization; - } +export function revokePermissionFromGroup(data: { + projectKey?: string; + groupName: string; + permission: string; + organization?: string; +}) { return post('/api/permissions/remove_group', data).catch(throwGlobalError); } @@ -165,93 +143,58 @@ export function removeProjectCreatorFromTemplate( return post('/api/permissions/remove_project_creator_from_template', { templateId, permission }); } -export interface PermissionUser { - login: string; - name: string; - email?: string; - permissions: string[]; - avatar?: string; -} - -export function getPermissionsUsersForComponent( - projectKey: string, - query?: string, - permission?: string, - organization?: string -): Promise { - const data: RequestData = { projectKey, ps: PAGE_SIZE }; - if (query) { - data.q = query; - } - if (permission) { - data.permission = permission; - } - if (organization) { - data.organization = organization; +export function getPermissionsUsersForComponent(data: { + projectKey: string; + q?: string; + permission?: string; + organization?: string; + p?: number; + ps?: number; +}): Promise<{ paging: Paging; users: PermissionUser[] }> { + if (!data.ps) { + data.ps = PAGE_SIZE; } - return getJSON('/api/permissions/users', data).then(r => r.users, throwGlobalError); -} - -export interface PermissionGroup { - id: string; - name: string; - description?: string; - permissions: string[]; + return getJSON('/api/permissions/users', data).catch(throwGlobalError); } -export function getPermissionsGroupsForComponent( - projectKey: string, - query: string = '', - permission?: string, - organization?: string -): Promise { - const data: RequestData = { projectKey, ps: PAGE_SIZE }; - if (query) { - data.q = query; - } - if (permission) { - data.permission = permission; - } - if (organization) { - data.organization = organization; +export function getPermissionsGroupsForComponent(data: { + projectKey: string; + q?: string; + permission?: string; + organization?: string; + p?: number; + ps?: number; +}): Promise<{ paging: Paging; groups: PermissionGroup[] }> { + if (!data.ps) { + data.ps = PAGE_SIZE; } - return getJSON('/api/permissions/groups', data).then(r => r.groups, throwGlobalError); + return getJSON('/api/permissions/groups', data).catch(throwGlobalError); } -export function getGlobalPermissionsUsers( - query?: string, - permission?: string, - organization?: string -): Promise { - const data: RequestData = { ps: PAGE_SIZE }; - if (query) { - data.q = query; - } - if (permission) { - data.permission = permission; - } - if (organization) { - data.organization = organization; +export function getGlobalPermissionsUsers(data: { + q?: string; + permission?: string; + organization?: string; + p?: number; + ps?: number; +}): Promise<{ paging: Paging; users: PermissionUser[] }> { + if (!data.ps) { + data.ps = PAGE_SIZE; } - return getJSON('/api/permissions/users', data).then(r => r.users); + return getJSON('/api/permissions/users', data); } -export function getGlobalPermissionsGroups( - query?: string, - permission?: string, - organization?: string -): Promise { - const data: RequestData = { ps: PAGE_SIZE }; - if (query) { - data.q = query; - } - if (permission) { - data.permission = permission; - } - if (organization) { - data.organization = organization; +export function getGlobalPermissionsGroups(data: { + q?: string; + permission?: string; + organization?: string; + p?: number; + ps?: number; +}): Promise<{ paging: Paging; groups: PermissionGroup[] }> { + if (!data.ps) { + data.ps = PAGE_SIZE; } - return getJSON('/api/permissions/groups', data).then(r => r.groups); + return getJSON('/api/permissions/groups', data); } export function getPermissionTemplateUsers( diff --git a/server/sonar-web/src/main/js/app/types.ts b/server/sonar-web/src/main/js/app/types.ts index 109e08c56ed..13d556d491b 100644 --- a/server/sonar-web/src/main/js/app/types.ts +++ b/server/sonar-web/src/main/js/app/types.ts @@ -102,6 +102,7 @@ export interface Component extends LightComponent { interface ComponentConfiguration { canApplyPermissionTemplate?: boolean; + canUpdateProjectVisibilityToPrivate?: boolean; extensions?: Extension[]; showBackgroundTasks?: boolean; showHistory?: boolean; @@ -536,6 +537,27 @@ export enum PeriodMode { PreviousVersion = 'previous_version' } +export interface Permission { + key: string; + name: string; + description: string; +} + +export interface PermissionGroup { + id?: string; + name: string; + description?: string; + permissions: string[]; +} + +export interface PermissionUser { + login: string; + name: string; + email?: string; + permissions: string[]; + avatar?: string; +} + export interface PermissionTemplate { defaultFor: string[]; id: string; diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx index 5d0d33f4fb8..85a1946055f 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersList.tsx @@ -21,8 +21,8 @@ import * as React from 'react'; import SearchForm from '../../shared/components/SearchForm'; import HoldersList from '../../shared/components/HoldersList'; import { translate } from '../../../../helpers/l10n'; -import { Organization } from '../../../../app/types'; -import { PermissionUser, PermissionGroup } from '../../../../api/permissions'; +import { Organization, Paging, PermissionGroup, PermissionUser } from '../../../../app/types'; +import ListFooter from '../../../../components/controls/ListFooter'; const PERMISSIONS_ORDER = ['admin', 'profileadmin', 'gateadmin', 'scan', 'provisioning']; @@ -31,7 +31,10 @@ interface Props { grantPermissionToGroup: (groupName: string, permission: string) => Promise; grantPermissionToUser: (login: string, permission: string) => Promise; groups: PermissionGroup[]; + groupsPaging: Paging; loadHolders: () => void; + loading?: boolean; + onLoadMore: (usersPageIndex: number, groupsPageIndex: number) => void; onFilter: (filter: string) => void; onSearch: (query: string) => void; onSelectPermission: (permission: string) => void; @@ -41,16 +44,12 @@ interface Props { revokePermissionFromUser: (login: string, permission: string) => Promise; selectedPermission?: string; users: PermissionUser[]; + usersPaging: Paging; } export default class AllHoldersList extends React.PureComponent { - componentDidMount() { - this.props.loadHolders(); - } - handleToggleUser = (user: PermissionUser, permission: string) => { const hasPermission = user.permissions.includes(permission); - if (hasPermission) { return this.props.revokePermissionFromUser(user.login, permission); } else { @@ -68,6 +67,13 @@ export default class AllHoldersList extends React.PureComponent { } }; + handleLoadMore = () => { + this.props.onLoadMore( + this.props.usersPaging.pageIndex + 1, + this.props.groupsPaging.pageIndex + 1 + ); + }; + render() { const l10nPrefix = this.props.organization ? 'organizations_permissions' : 'global_permissions'; const permissions = PERMISSIONS_ORDER.map(p => ({ @@ -76,22 +82,33 @@ export default class AllHoldersList extends React.PureComponent { description: translate(l10nPrefix, p, 'desc') })); + const count = + (this.props.filter !== 'users' ? this.props.groups.length : 0) + + (this.props.filter !== 'groups' ? this.props.users.length : 0); + const total = + (this.props.filter !== 'users' ? this.props.groupsPaging.total : 0) + + (this.props.filter !== 'groups' ? this.props.usersPaging.total : 0); + return ( - - - + <> + + + + + ); } } diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersListContainer.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersListContainer.tsx deleted file mode 100644 index 66203598154..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/AllHoldersListContainer.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { Dispatch } from 'redux'; -import { connect } from 'react-redux'; -import AllHoldersList from './AllHoldersList'; -import { - loadHolders, - grantToUser, - revokeFromUser, - grantToGroup, - revokeFromGroup, - updateFilter, - updateQuery, - selectPermission -} from '../store/actions'; -import { - getPermissionsAppUsers, - getPermissionsAppGroups, - getPermissionsAppQuery, - getPermissionsAppFilter, - getPermissionsAppSelectedPermission, - Store -} from '../../../../store/rootReducer'; -import { Organization } from '../../../../app/types'; - -interface OwnProps { - organization?: Organization; -} - -const mapStateToProps = (state: Store) => ({ - filter: getPermissionsAppFilter(state), - groups: getPermissionsAppGroups(state), - query: getPermissionsAppQuery(state), - selectedPermission: getPermissionsAppSelectedPermission(state), - users: getPermissionsAppUsers(state) -}); - -const mapDispatchToProps = (dispatch: Dispatch, ownProps: OwnProps) => { - const organizationKey = ownProps.organization ? ownProps.organization.key : undefined; - return { - grantPermissionToGroup: (groupName: string, permission: string) => - dispatch(grantToGroup(groupName, permission, organizationKey)), - grantPermissionToUser: (login: string, permission: string) => - dispatch(grantToUser(login, permission, organizationKey)), - loadHolders: () => dispatch(loadHolders(organizationKey)), - onFilter: (filter: string) => dispatch(updateFilter(filter, organizationKey)), - onSearch: (query: string) => dispatch(updateQuery(query, organizationKey)), - onSelectPermission: (permission: string) => - dispatch(selectPermission(permission, organizationKey)), - revokePermissionFromGroup: (groupName: string, permission: string) => - dispatch(revokeFromGroup(groupName, permission, organizationKey)), - revokePermissionFromUser: (login: string, permission: string) => - dispatch(revokeFromUser(login, permission, organizationKey)) - }; -}; - -export default connect( - mapStateToProps, - mapDispatchToProps -)(AllHoldersList); diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx index e9b0a329a90..fa13917f4ea 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/App.tsx @@ -19,12 +19,13 @@ */ import * as React from 'react'; import Helmet from 'react-helmet'; +import { without } from 'lodash'; import PageHeader from './PageHeader'; -import AllHoldersListContainer from './AllHoldersListContainer'; -import PageError from '../../shared/components/PageError'; +import AllHoldersList from './AllHoldersList'; +import * as api from '../../../../api/permissions'; import Suggestions from '../../../../app/components/embed-docs-modal/Suggestions'; import { translate } from '../../../../helpers/l10n'; -import { Organization } from '../../../../app/types'; +import { Organization, Paging, PermissionGroup, PermissionUser } from '../../../../app/types'; import forSingleOrganization from '../../../organizations/forSingleOrganization'; import '../../styles.css'; @@ -32,16 +33,299 @@ interface Props { organization?: Organization; } -function App({ organization }: Props) { - return ( -
- - - - - -
- ); +interface State { + filter: string; + groups: PermissionGroup[]; + groupsPaging: Paging; + loading: boolean; + query: string; + selectedPermission?: string; + users: PermissionUser[]; + usersPaging: Paging; +} + +export class App extends React.PureComponent { + mounted = false; + + constructor(props: Props) { + super(props); + this.state = { + filter: 'all', + groups: [], + groupsPaging: { pageIndex: 1, pageSize: 100, total: 0 }, + loading: true, + query: '', + users: [], + usersPaging: { pageIndex: 1, pageSize: 100, total: 0 } + }; + } + + componentDidMount() { + this.mounted = true; + this.loadHolders(); + } + + componentWillUnmount() { + this.mounted = false; + } + + loadUsersAndGroups = (userPage?: number, groupsPage?: number) => { + const { organization } = this.props; + const { filter, query, selectedPermission } = this.state; + + const getUsers = + filter !== 'groups' + ? api.getGlobalPermissionsUsers({ + q: query || undefined, + permission: selectedPermission, + organization: organization && organization.key, + p: userPage + }) + : Promise.resolve({ + paging: { + pageIndex: 1, + pageSize: 100, + total: 0 + }, + users: [] + }); + + const getGroups = + filter !== 'users' + ? api.getGlobalPermissionsGroups({ + q: query || undefined, + permission: selectedPermission, + organization: organization && organization.key, + p: groupsPage + }) + : Promise.resolve({ + paging: { pageIndex: 1, pageSize: 100, total: 0 }, + groups: [] + }); + + return Promise.all([getUsers, getGroups]); + }; + + loadHolders = () => { + this.setState({ loading: true }); + return this.loadUsersAndGroups().then(([usersResponse, groupsResponse]) => { + if (this.mounted) { + this.setState({ + groups: groupsResponse.groups, + groupsPaging: groupsResponse.paging, + loading: false, + users: usersResponse.users, + usersPaging: usersResponse.paging + }); + } + }, this.stopLoading); + }; + + onLoadMore = () => { + this.setState({ loading: true }); + return this.loadUsersAndGroups( + this.state.usersPaging.pageIndex + 1, + this.state.groupsPaging.pageIndex + 1 + ).then(([usersResponse, groupsResponse]) => { + if (this.mounted) { + this.setState(({ groups, users }) => ({ + groups: [...groups, ...groupsResponse.groups], + groupsPaging: groupsResponse.paging, + loading: false, + users: [...users, ...usersResponse.users], + usersPaging: usersResponse.paging + })); + } + }, this.stopLoading); + }; + + onFilter = (filter: string) => { + this.setState({ filter }, this.loadHolders); + }; + + onSearch = (query: string) => { + this.setState({ query }, this.loadHolders); + }; + + onSelectPermission = (permission: string) => { + this.setState( + ({ selectedPermission }) => ({ + selectedPermission: selectedPermission !== permission ? permission : undefined + }), + this.loadHolders + ); + }; + + addPermissionToGroup = (groups: PermissionGroup[], group: string, permission: string) => { + return groups.map( + candidate => + candidate.name === group + ? { ...candidate, permissions: [...candidate.permissions, permission] } + : candidate + ); + }; + + addPermissionToUser = (users: PermissionUser[], user: string, permission: string) => { + return users.map( + candidate => + candidate.login === user + ? { ...candidate, permissions: [...candidate.permissions, permission] } + : candidate + ); + }; + + removePermissionFromGroup = (groups: PermissionGroup[], group: string, permission: string) => { + return groups.map( + candidate => + candidate.name === group + ? { ...candidate, permissions: without(candidate.permissions, permission) } + : candidate + ); + }; + + removePermissionFromUser = (users: PermissionUser[], user: string, permission: string) => { + return users.map( + candidate => + candidate.login === user + ? { ...candidate, permissions: without(candidate.permissions, permission) } + : candidate + ); + }; + + grantPermissionToGroup = (group: string, permission: string) => { + if (this.mounted) { + this.setState(({ groups }) => ({ + groups: this.addPermissionToGroup(groups, group, permission) + })); + return api + .grantPermissionToGroup({ + groupName: group, + permission, + organization: this.props.organization && this.props.organization.key + }) + .then( + () => {}, + () => { + if (this.mounted) { + this.setState(({ groups }) => ({ + groups: this.removePermissionFromGroup(groups, group, permission) + })); + } + } + ); + } + return Promise.resolve(); + }; + + grantPermissionToUser = (user: string, permission: string) => { + if (this.mounted) { + this.setState(({ users }) => ({ + users: this.addPermissionToUser(users, user, permission) + })); + return api + .grantPermissionToUser({ + login: user, + permission, + organization: this.props.organization && this.props.organization.key + }) + .then( + () => {}, + () => { + if (this.mounted) { + this.setState(({ users }) => ({ + users: this.removePermissionFromUser(users, user, permission) + })); + } + } + ); + } + return Promise.resolve(); + }; + + revokePermissionFromGroup = (group: string, permission: string) => { + if (this.mounted) { + this.setState(({ groups }) => ({ + groups: this.removePermissionFromGroup(groups, group, permission) + })); + return api + .revokePermissionFromGroup({ + groupName: group, + permission, + organization: this.props.organization && this.props.organization.key + }) + .then( + () => {}, + () => { + if (this.mounted) { + this.setState(({ groups }) => ({ + groups: this.addPermissionToGroup(groups, group, permission) + })); + } + } + ); + } + return Promise.resolve(); + }; + + revokePermissionFromUser = (user: string, permission: string) => { + if (this.mounted) { + this.setState(({ users }) => ({ + users: this.removePermissionFromUser(users, user, permission) + })); + return api + .revokePermissionFromUser({ + login: user, + permission, + organization: this.props.organization && this.props.organization.key + }) + .then( + () => {}, + () => { + if (this.mounted) { + this.setState(({ users }) => ({ + users: this.addPermissionToUser(users, user, permission) + })); + } + } + ); + } + return Promise.resolve(); + }; + + stopLoading = () => { + if (this.mounted) { + this.setState({ loading: false }); + } + }; + + render() { + return ( +
+ + + + +
+ ); + } } export default forSingleOrganization(App); diff --git a/server/sonar-web/src/main/js/apps/permissions/global/components/PageHeader.js b/server/sonar-web/src/main/js/apps/permissions/global/components/PageHeader.tsx similarity index 76% rename from server/sonar-web/src/main/js/apps/permissions/global/components/PageHeader.js rename to server/sonar-web/src/main/js/apps/permissions/global/components/PageHeader.tsx index 79f38b6bccd..930adc798cb 100644 --- a/server/sonar-web/src/main/js/apps/permissions/global/components/PageHeader.js +++ b/server/sonar-web/src/main/js/apps/permissions/global/components/PageHeader.tsx @@ -17,22 +17,15 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import React from 'react'; -import { connect } from 'react-redux'; +import * as React from 'react'; import { translate } from '../../../../helpers/l10n'; -import { isPermissionsAppLoading } from '../../../../store/rootReducer'; -class PageHeader extends React.PureComponent { - /*:: props: { - loading?: boolean, - organization?: {} - }; -*/ - - static defaultProps = { - loading: false - }; +interface Props { + loading?: boolean; + organization?: {}; +} +export default class PageHeader extends React.PureComponent { render() { const title = this.props.organization ? translate('permissions.page') @@ -53,9 +46,3 @@ class PageHeader extends React.PureComponent { ); } } - -const mapStateToProps = state => ({ - loading: isPermissionsAppLoading(state) -}); - -export default connect(mapStateToProps)(PageHeader); diff --git a/server/sonar-web/src/main/js/apps/permissions/global/store/actions.js b/server/sonar-web/src/main/js/apps/permissions/global/store/actions.js deleted file mode 100644 index 1966626300d..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/global/store/actions.js +++ /dev/null @@ -1,179 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -// @flow -import * as api from '../../../../api/permissions'; -import { parseError } from '../../../../helpers/request'; -import { - raiseError, - REQUEST_HOLDERS, - RECEIVE_HOLDERS_SUCCESS, - UPDATE_QUERY, - UPDATE_FILTER, - SELECT_PERMISSION, - GRANT_PERMISSION_TO_USER, - REVOKE_PERMISSION_TO_USER, - GRANT_PERMISSION_TO_GROUP, - REVOKE_PERMISSION_FROM_GROUP -} from '../../shared/store/actions'; -import { - getPermissionsAppQuery, - getPermissionsAppFilter, - getPermissionsAppSelectedPermission -} from '../../../../store/rootReducer'; - -/*:: -type Dispatch = Object => void; -*/ -/*:: -type GetState = () => Object; -*/ - -export const loadHolders = (organization /*: ?string */) => ( - dispatch /*: Dispatch */, - getState /*: GetState */ -) => { - const query = getPermissionsAppQuery(getState()); - const filter = getPermissionsAppFilter(getState()); - const selectedPermission = getPermissionsAppSelectedPermission(getState()); - - dispatch({ type: REQUEST_HOLDERS, query }); - - const requests = []; - - if (filter !== 'groups') { - requests.push(api.getGlobalPermissionsUsers(query, selectedPermission, organization)); - } else { - requests.push(Promise.resolve([])); - } - - if (filter !== 'users') { - requests.push(api.getGlobalPermissionsGroups(query, selectedPermission, organization)); - } else { - requests.push(Promise.resolve([])); - } - - return Promise.all(requests) - .then(responses => - dispatch({ - type: RECEIVE_HOLDERS_SUCCESS, - users: responses[0], - groups: responses[1], - query - }) - ) - .catch(e => { - return parseError(e).then(message => dispatch(raiseError(message))); - }); -}; - -export const updateQuery = (query /*: string */ = '', organization /*: ?string */) => ( - dispatch /*: Dispatch */ -) => { - dispatch({ type: UPDATE_QUERY, query }); - dispatch(loadHolders(organization)); -}; - -export const updateFilter = (filter /*: string */, organization /*: ?string */) => ( - dispatch /*: Dispatch */ -) => { - dispatch({ type: UPDATE_FILTER, filter }); - dispatch(loadHolders(organization)); -}; - -export const selectPermission = (permission /*: string */, organization /*: ?string */) => ( - dispatch /*: Dispatch */, - getState /*: GetState */ -) => { - const selectedPermission = getPermissionsAppSelectedPermission(getState()); - if (selectedPermission !== permission) { - dispatch({ type: SELECT_PERMISSION, permission }); - } else { - dispatch({ type: SELECT_PERMISSION, permission: null }); - } - dispatch(loadHolders(organization)); -}; - -export const grantToUser = ( - login /*: string */, - permission /*: string */, - organization /*: ?string */ -) => (dispatch /*: Dispatch */) => { - return api - .grantPermissionToUser(null, login, permission, organization) - .then(() => { - dispatch({ type: GRANT_PERMISSION_TO_USER, login, permission }); - }) - .catch(e => { - return parseError(e).then(message => dispatch(raiseError(message))); - }); -}; - -export const revokeFromUser = ( - login /*: string */, - permission /*: string */, - organization /*: ?string */ -) => (dispatch /*: Dispatch */) => { - return api - .revokePermissionFromUser(null, login, permission, organization) - .then(() => { - dispatch({ type: REVOKE_PERMISSION_TO_USER, login, permission }); - }) - .catch(e => { - return parseError(e).then(message => dispatch(raiseError(message))); - }); -}; - -export const grantToGroup = ( - groupName /*: string */, - permission /*: string */, - organization /*: ?string */ -) => (dispatch /*: Dispatch */) => { - return api - .grantPermissionToGroup(null, groupName, permission, organization) - .then(() => { - dispatch({ - type: GRANT_PERMISSION_TO_GROUP, - groupName, - permission - }); - }) - .catch(e => { - return parseError(e).then(message => dispatch(raiseError(message))); - }); -}; - -export const revokeFromGroup = ( - groupName /*: string */, - permission /*: string */, - organization /*: ?string */ -) => (dispatch /*: Dispatch */) => { - return api - .revokePermissionFromGroup(null, groupName, permission, organization) - .then(() => { - dispatch({ - type: REVOKE_PERMISSION_FROM_GROUP, - groupName, - permission - }); - }) - .catch(e => { - return parseError(e).then(message => dispatch(raiseError(message))); - }); -}; diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.js b/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx similarity index 52% rename from server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.js rename to server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx index 1fbf421aa71..ea9119ad4c3 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.js +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/AllHoldersList.tsx @@ -17,54 +17,43 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import { without } from 'lodash'; import SearchForm from '../../shared/components/SearchForm'; import HoldersList from '../../shared/components/HoldersList'; import { translate } from '../../../../helpers/l10n'; import { PERMISSIONS_ORDER_BY_QUALIFIER } from '../constants'; -import { Visibility } from '../../../../app/types'; +import { + Component, + Paging, + PermissionGroup, + PermissionUser, + Visibility +} from '../../../../app/types'; +import ListFooter from '../../../../components/controls/ListFooter'; -/*:: -type Props = {| - component: { - configuration?: { - canApplyPermissionTemplate: boolean, - canUpdateProjectVisibilityToPrivate: boolean - }, - key: string, - organization: string, - qualifier: string, - visibility: string - }, - filter: string, - grantPermissionToGroup: (group: string, permission: string) => Promise, - grantPermissionToUser: (user: string, permission: string) => Promise, - groups: Array<{ - name: string, - permissions: Array - }>, - onFilterChange: string => void, - onPermissionSelect: (string | void) => void, - onQueryChange: string => void, - query: string, - revokePermissionFromGroup: (group: string, permission: string) => Promise, - revokePermissionFromUser: (user: string, permission: string) => Promise, - selectedPermission: ?string, - visibility: string, - users: Array<{ - login: string, - name: string, - permissions: Array - }> -|}; -*/ - -export default class AllHoldersList extends React.PureComponent { - /*:: props: Props; */ +interface Props { + component: Component; + filter: string; + grantPermissionToGroup: (group: string, permission: string) => Promise; + grantPermissionToUser: (user: string, permission: string) => Promise; + groups: PermissionGroup[]; + groupsPaging: Paging; + onLoadMore: (usersPageIndex: number, groupsPageIndex: number) => void; + onFilterChange: (filter: string) => void; + onPermissionSelect: (permissions?: string) => void; + onQueryChange: (query: string) => void; + query: string; + revokePermissionFromGroup: (group: string, permission: string) => Promise; + revokePermissionFromUser: (user: string, permission: string) => Promise; + selectedPermission?: string; + users: PermissionUser[]; + usersPaging: Paging; + visibility?: Visibility; +} - handleToggleUser = (user /*: Object */, permission /*: string */) => { +export default class AllHoldersList extends React.PureComponent { + handleToggleUser = (user: PermissionUser, permission: string) => { const hasPermission = user.permissions.includes(permission); if (hasPermission) { @@ -74,7 +63,7 @@ export default class AllHoldersList extends React.PureComponent { } }; - handleToggleGroup = (group /*: Object */, permission /*: string */) => { + handleToggleGroup = (group: PermissionGroup, permission: string) => { const hasPermission = group.permissions.includes(permission); if (hasPermission) { @@ -84,10 +73,17 @@ export default class AllHoldersList extends React.PureComponent { } }; - handleSelectPermission = (permission /*: string | void */) => { + handleSelectPermission = (permission?: string) => { this.props.onPermissionSelect(permission); }; + handleLoadMore = () => { + this.props.onLoadMore( + this.props.usersPaging.pageIndex + 1, + this.props.groupsPaging.pageIndex + 1 + ); + }; + render() { let order = PERMISSIONS_ORDER_BY_QUALIFIER[this.props.component.qualifier]; if (this.props.visibility === Visibility.Public) { @@ -100,22 +96,32 @@ export default class AllHoldersList extends React.PureComponent { description: translate('projects_role', p, 'desc') })); + const count = + (this.props.filter !== 'users' ? this.props.groups.length : 0) + + (this.props.filter !== 'groups' ? this.props.users.length : 0); + const total = + (this.props.filter !== 'users' ? this.props.groupsPaging.total : 0) + + (this.props.filter !== 'groups' ? this.props.usersPaging.total : 0); + return ( - - - + <> + + + + + ); } } diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/App.js b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx similarity index 60% rename from server/sonar-web/src/main/js/apps/permissions/project/components/App.js rename to server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx index 28f20e3ef74..a67598d44c6 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/App.js +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/App.tsx @@ -17,8 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -// @flow -import React from 'react'; +import * as React from 'react'; import Helmet from 'react-helmet'; import { without } from 'lodash'; import AllHoldersList from './AllHoldersList'; @@ -26,62 +25,48 @@ import PageHeader from './PageHeader'; import PublicProjectDisclaimer from './PublicProjectDisclaimer'; import UpgradeOrganizationBox from '../../../../components/common/UpgradeOrganizationBox'; import VisibilitySelector from '../../../../components/common/VisibilitySelector'; -import PageError from '../../shared/components/PageError'; import * as api from '../../../../api/permissions'; import { translate } from '../../../../helpers/l10n'; +import { + Component, + Paging, + PermissionGroup, + PermissionUser, + Visibility +} from '../../../../app/types'; import '../../styles.css'; -import { Visibility } from '../../../../app/types'; - -/*:: -export type Props = {| - component: { - configuration?: { - canApplyPermissionTemplate: boolean, - canUpdateProjectVisibilityToPrivate: boolean - }, - key: string, - name: string, - organization: string, - qualifier: string, - visibility: string - }, - onComponentChange: (changes: {}) => void -|}; -*/ - -/*:: -export type State = {| - disclaimer: boolean, - filter: string, - groups: Array<{ - name: string, - permissions: Array - }>, - loading: boolean, - query: string, - selectedPermission?: string, - users: Array<{ - login: string, - name: string, - permissions: Array - }> -|}; -*/ - -export default class App extends React.PureComponent { - /*:: mounted: boolean; */ - /*:: props: Props; */ - /*:: state: State; */ - - constructor(props /*: Props */) { + +interface Props { + component: Component; + onComponentChange: (changes: Partial) => void; +} + +interface State { + disclaimer: boolean; + filter: string; + groups: PermissionGroup[]; + groupsPaging: Paging; + loading: boolean; + query: string; + selectedPermission?: string; + users: PermissionUser[]; + usersPaging: Paging; +} + +export default class App extends React.PureComponent { + mounted = false; + + constructor(props: Props) { super(props); this.state = { disclaimer: false, filter: 'all', groups: [], + groupsPaging: { pageIndex: 1, pageSize: 100, total: 0 }, loading: true, query: '', - users: [] + users: [], + usersPaging: { pageIndex: 1, pageSize: 100, total: 0 } }; } @@ -100,7 +85,7 @@ export default class App extends React.PureComponent { } }; - loadHolders = () => { + loadHolders = (usersPageIndex?: number, groupsPageIndex?: number) => { if (this.mounted) { this.setState({ loading: true }); @@ -109,48 +94,64 @@ export default class App extends React.PureComponent { const getUsers = filter !== 'groups' - ? api.getPermissionsUsersForComponent( - component.key, - query, - selectedPermission, - component.organization - ) - : Promise.resolve([]); + ? api.getPermissionsUsersForComponent({ + projectKey: component.key, + q: query || undefined, + permission: selectedPermission, + organization: component.organization, + p: usersPageIndex + }) + : Promise.resolve({ + paging: { pageIndex: 1, pageSize: 100, total: 0 }, + users: [] + }); const getGroups = filter !== 'users' - ? api.getPermissionsGroupsForComponent( - component.key, - query, - selectedPermission, - component.organization - ) - : Promise.resolve([]); + ? api.getPermissionsGroupsForComponent({ + projectKey: component.key, + q: query || undefined, + permission: selectedPermission, + organization: component.organization, + p: groupsPageIndex + }) + : Promise.resolve({ + paging: { pageIndex: 1, pageSize: 100, total: 0 }, + groups: [] + }); Promise.all([getUsers, getGroups]).then(responses => { if (this.mounted) { - this.setState({ loading: false, groups: responses[1], users: responses[0] }); + this.setState(state => ({ + loading: false, + groups: groupsPageIndex + ? [...state.groups, ...responses[1].groups] + : responses[1].groups, + groupsPaging: responses[1].paging, + users: usersPageIndex ? [...state.users, ...responses[0].users] : responses[0].users, + usersPaging: responses[0].paging + })); } }, this.stopLoading); } }; - handleFilterChange = (filter /*: string */) => { + handleFilterChange = (filter: string) => { if (this.mounted) { this.setState({ filter }, this.loadHolders); } }; - handleQueryChange = (query /*: string */) => { + handleQueryChange = (query: string) => { if (this.mounted) { this.setState({ query }, this.loadHolders); } }; - handlePermissionSelect = (selectedPermission /*: ?string */) => { + handlePermissionSelect = (selectedPermission?: string) => { if (this.mounted) { this.setState( - (state /*: State */) => ({ + (state: State) => ({ selectedPermission: state.selectedPermission === selectedPermission ? undefined : selectedPermission }), @@ -159,49 +160,56 @@ export default class App extends React.PureComponent { } }; - addPermissionToGroup = (group /*: string */, permission /*: string */) => - this.state.groups.map( + addPermissionToGroup = (group: string, permission: string) => { + return this.state.groups.map( candidate => candidate.name === group ? { ...candidate, permissions: [...candidate.permissions, permission] } : candidate ); + }; - addPermissionToUser = (user /*: string */, permission /*: string */) => - this.state.users.map( + addPermissionToUser = (user: string, permission: string) => { + return this.state.users.map( candidate => candidate.login === user ? { ...candidate, permissions: [...candidate.permissions, permission] } : candidate ); + }; - removePermissionFromGroup = (group /*: string */, permission /*: string */) => - this.state.groups.map( + removePermissionFromGroup = (group: string, permission: string) => { + return this.state.groups.map( candidate => candidate.name === group ? { ...candidate, permissions: without(candidate.permissions, permission) } : candidate ); + }; - removePermissionFromUser = (user /*: string */, permission /*: string */) => - this.state.users.map( + removePermissionFromUser = (user: string, permission: string) => { + return this.state.users.map( candidate => candidate.login === user ? { ...candidate, permissions: without(candidate.permissions, permission) } : candidate ); + }; - grantPermissionToGroup = (group /*: string */, permission /*: string */) => { + grantPermissionToGroup = (group: string, permission: string) => { if (this.mounted) { - this.setState({ loading: true, groups: this.addPermissionToGroup(group, permission) }); + this.setState({ + loading: true, + groups: this.addPermissionToGroup(group, permission) + }); return api - .grantPermissionToGroup( - this.props.component.key, - group, + .grantPermissionToGroup({ + projectKey: this.props.component.key, + groupName: group, permission, - this.props.component.organization - ) - .then(this.stopLoading, error => { + organization: this.props.component.organization + }) + .then(this.stopLoading, () => { if (this.mounted) { this.setState({ loading: false, @@ -213,17 +221,20 @@ export default class App extends React.PureComponent { return Promise.resolve(); }; - grantPermissionToUser = (user /*: string */, permission /*: string */) => { + grantPermissionToUser = (user: string, permission: string) => { if (this.mounted) { - this.setState({ loading: true, users: this.addPermissionToUser(user, permission) }); + this.setState({ + loading: true, + users: this.addPermissionToUser(user, permission) + }); return api - .grantPermissionToUser( - this.props.component.key, - user, + .grantPermissionToUser({ + projectKey: this.props.component.key, + login: user, permission, - this.props.component.organization - ) - .then(this.stopLoading, error => { + organization: this.props.component.organization + }) + .then(this.stopLoading, () => { if (this.mounted) { this.setState({ loading: false, @@ -235,17 +246,20 @@ export default class App extends React.PureComponent { return Promise.resolve(); }; - revokePermissionFromGroup = (group /*: string */, permission /*: string */) => { + revokePermissionFromGroup = (group: string, permission: string) => { if (this.mounted) { - this.setState({ loading: true, groups: this.removePermissionFromGroup(group, permission) }); + this.setState({ + loading: true, + groups: this.removePermissionFromGroup(group, permission) + }); return api - .revokePermissionFromGroup( - this.props.component.key, - group, + .revokePermissionFromGroup({ + projectKey: this.props.component.key, + groupName: group, permission, - this.props.component.organization - ) - .then(this.stopLoading, error => { + organization: this.props.component.organization + }) + .then(this.stopLoading, () => { if (this.mounted) { this.setState({ loading: false, @@ -257,17 +271,20 @@ export default class App extends React.PureComponent { return Promise.resolve(); }; - revokePermissionFromUser = (user /*: string */, permission /*: string */) => { + revokePermissionFromUser = (user: string, permission: string) => { if (this.mounted) { - this.setState({ loading: true, users: this.removePermissionFromUser(user, permission) }); + this.setState({ + loading: true, + users: this.removePermissionFromUser(user, permission) + }); return api - .revokePermissionFromUser( - this.props.component.key, - user, + .revokePermissionFromUser({ + projectKey: this.props.component.key, + login: user, permission, - this.props.component.organization - ) - .then(this.stopLoading, error => { + organization: this.props.component.organization + }) + .then(this.stopLoading, () => { if (this.mounted) { this.setState({ loading: false, @@ -279,7 +296,7 @@ export default class App extends React.PureComponent { return Promise.resolve(); }; - handleVisibilityChange = (visibility /*: string */) => { + handleVisibilityChange = (visibility: string) => { if (visibility === Visibility.Public) { this.openDisclaimer(); } else { @@ -293,8 +310,10 @@ export default class App extends React.PureComponent { () => { this.loadHolders(); }, - error => { - this.props.onComponentChange({ visibility: Visibility.Private }); + () => { + this.props.onComponentChange({ + visibility: Visibility.Private + }); } ); }; @@ -305,8 +324,10 @@ export default class App extends React.PureComponent { () => { this.loadHolders(); }, - error => { - this.props.onComponentChange({ visibility: Visibility.Public }); + () => { + this.props.onComponentChange({ + visibility: Visibility.Public + }); } ); }; @@ -323,6 +344,10 @@ export default class App extends React.PureComponent { } }; + handleLoadMore = (usersPageIndex: number, groupsPageIndex: number) => { + this.loadHolders(usersPageIndex, groupsPageIndex); + }; + render() { const canTurnToPrivate = this.props.component.configuration != null && @@ -337,7 +362,6 @@ export default class App extends React.PureComponent { loadHolders={this.loadHolders} loading={this.state.loading} /> -
diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.js b/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts similarity index 89% rename from server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.js rename to server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts index 2dcc1210a03..b8f0073e5bb 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.js +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/AppContainer.ts @@ -19,9 +19,9 @@ */ import { connect } from 'react-redux'; import App from './App'; -import { getCurrentUser } from '../../../../store/rootReducer'; +import { getCurrentUser, Store } from '../../../../store/rootReducer'; -const mapStateToProps = state => ({ +const mapStateToProps = (state: Store) => ({ currentUser: getCurrentUser(state) }); diff --git a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx index aec156ffd26..e7c6947c1a9 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/project/components/PageHeader.tsx @@ -68,7 +68,7 @@ export default class PageHeader extends React.PureComponent { const visibilityDescription = component.qualifier === 'TRK' && component.visibility ? translate('visibility', component.visibility, 'description', component.qualifier) - : null; + : undefined; return (
@@ -95,7 +95,7 @@ export default class PageHeader extends React.PureComponent {

{description}

- {visibilityDescription != null &&

{visibilityDescription}

} + {visibilityDescription &&

{visibilityDescription}

}
); diff --git a/server/sonar-web/src/main/js/apps/permissions/project/constants.js b/server/sonar-web/src/main/js/apps/permissions/project/constants.tsx similarity index 94% rename from server/sonar-web/src/main/js/apps/permissions/project/constants.js rename to server/sonar-web/src/main/js/apps/permissions/project/constants.tsx index 13a6bee92c5..709fd13ab73 100644 --- a/server/sonar-web/src/main/js/apps/permissions/project/constants.js +++ b/server/sonar-web/src/main/js/apps/permissions/project/constants.tsx @@ -30,7 +30,7 @@ export const PERMISSIONS_ORDER_FOR_VIEW = ['user', 'admin']; export const PERMISSIONS_ORDER_FOR_DEV = ['user', 'admin']; -export const PERMISSIONS_ORDER_BY_QUALIFIER = { +export const PERMISSIONS_ORDER_BY_QUALIFIER: { [index: string]: string[] } = { TRK: PERMISSIONS_ORDER_FOR_PROJECT, VW: PERMISSIONS_ORDER_FOR_VIEW, SVW: PERMISSIONS_ORDER_FOR_VIEW, diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx index d8750562e0c..dac80f50c86 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/GroupHolder.tsx @@ -21,7 +21,7 @@ import * as React from 'react'; import { without } from 'lodash'; import Checkbox from '../../../../components/controls/Checkbox'; import GroupIcon from '../../../../components/icons-components/GroupIcon'; -import { PermissionGroup } from '../../../../api/permissions'; +import { PermissionGroup } from '../../../../app/types'; interface Props { group: PermissionGroup; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx index 544f3ce041b..791c42a1760 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/HoldersList.tsx @@ -18,24 +18,30 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import { groupBy } from 'lodash'; import UserHolder from './UserHolder'; import GroupHolder from './GroupHolder'; -import PermissionHeader, { Permission } from './PermissionHeader'; -import { PermissionUser, PermissionGroup } from '../../../../api/permissions'; +import PermissionHeader from './PermissionHeader'; import { translate } from '../../../../helpers/l10n'; +import { Permission, PermissionGroup, PermissionUser } from '../../../../app/types'; interface Props { - permissions: Permission[]; - users: PermissionUser[]; + loading?: boolean; groups: PermissionGroup[]; - selectedPermission?: string; - showPublicProjectsWarning?: boolean; onSelectPermission: (permission: string) => void; - onToggleUser: (user: PermissionUser, permission: string) => Promise; onToggleGroup: (group: PermissionGroup, permission: string) => Promise; + onToggleUser: (user: PermissionUser, permission: string) => Promise; + permissions: Permission[]; + selectedPermission?: string; + showPublicProjectsWarning?: boolean; + users: PermissionUser[]; } export default class HoldersList extends React.PureComponent { + isPermissionUser(item: PermissionGroup | PermissionUser): item is PermissionUser { + return (item as PermissionUser).login !== undefined; + } + renderTableHeader() { const { onSelectPermission, selectedPermission, showPublicProjectsWarning } = this.props; const cells = this.props.permissions.map(p => ( @@ -66,10 +72,14 @@ export default class HoldersList extends React.PureComponent { ); } - render() { - const permissionsOrder = this.props.permissions.map(p => p.key); + renderItem(item: PermissionUser | PermissionGroup, permissionsOrder: string[]) { + return this.isPermissionUser(item) + ? this.renderUser(item, permissionsOrder) + : this.renderGroup(item, permissionsOrder); + } - const users = this.props.users.map(user => ( + renderUser(user: PermissionUser, permissionsOrder: string[]) { + return ( { selectedPermission={this.props.selectedPermission} user={user} /> - )); + ); + } - const groups = this.props.groups.map(group => ( + renderGroup(group: PermissionGroup, permissionsOrder: string[]) { + return ( { permissionsOrder={permissionsOrder} selectedPermission={this.props.selectedPermission} /> - )); + ); + } + + render() { + const permissionsOrder = this.props.permissions.map(p => p.key); + const items = [...this.props.users, ...this.props.groups].sort((a, b) => { + return a.name < b.name ? -1 : 1; + }); + const { true: itemWithPermissions = [], false: itemWithoutPermissions = [] } = groupBy( + items, + item => item.permissions.length > 0 + ); return (
{this.renderTableHeader()} - {users.length === 0 && groups.length === 0 && this.renderEmpty()} - {users} - {groups} + {items.length === 0 && !this.props.loading && this.renderEmpty()} + {itemWithPermissions.map(item => this.renderItem(item, permissionsOrder))} + {itemWithPermissions.length > 0 && + itemWithoutPermissions.length > 0 && ( + <> + + + {/* Keep correct zebra colors in the table */} + + )} + {itemWithoutPermissions.map(item => this.renderItem(item, permissionsOrder))}
+
diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.tsx deleted file mode 100644 index cd2f3c0e89e..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/PageError.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { getPermissionsAppError, Store } from '../../../../store/rootReducer'; - -interface Props { - message: string; -} - -function PageError({ message }: Props) { - if (!message) { - return null; - } - - return
{message}
; -} - -const mapStateToProps = (state: Store) => ({ - message: getPermissionsAppError(state) -}); - -export default connect(mapStateToProps)(PageError); diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx index 0c3a6f6865a..a353a5fe549 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/PermissionHeader.tsx @@ -22,12 +22,7 @@ import HelpTooltip from '../../../../components/controls/HelpTooltip'; import InstanceMessage from '../../../../components/common/InstanceMessage'; import Tooltip from '../../../../components/controls/Tooltip'; import { translate, translateWithParameters } from '../../../../helpers/l10n'; - -export interface Permission { - key: string; - name: string; - description: string; -} +import { Permission } from '../../../../app/types'; interface Props { onSelectPermission: (permission: string) => void; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx index f52ad4eab32..e3a775b02b5 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/UserHolder.tsx @@ -21,8 +21,8 @@ import * as React from 'react'; import { without } from 'lodash'; import Avatar from '../../../../components/ui/Avatar'; import Checkbox from '../../../../components/controls/Checkbox'; -import { PermissionUser } from '../../../../api/permissions'; import { translate } from '../../../../helpers/l10n'; +import { PermissionUser } from '../../../../app/types'; interface Props { user: PermissionUser; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx index 4d071c05a89..05def6fb2c7 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/HoldersList-test.tsx @@ -25,12 +25,14 @@ const permissions = [{ key: 'bar', name: 'bar', description: 'foo' }]; const groups = [ { id: 'foobar', name: 'Foobar', permissions: ['bar'] }, - { id: 'barbaz', name: 'Barbaz', permissions: ['bar'] } + { id: 'barbaz', name: 'Barbaz', permissions: ['bar'] }, + { id: 'abc', name: 'abc', permissions: [] } ]; const users = [ { login: 'foobar', name: 'Foobar', permissions: ['bar'] }, - { login: 'barbaz', name: 'Barbaz', permissions: ['bar'] } + { login: 'barbaz', name: 'Barbaz', permissions: ['bar'] }, + { login: 'bcd', name: 'bcd', permissions: [] } ]; const elementsContainer = ( diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap index 03cfe1cce5d..811717f06f4 100644 --- a/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/permissions/shared/components/__tests__/__snapshots__/HoldersList-test.tsx.snap @@ -27,8 +27,17 @@ exports[`should display users and groups 1`] = ` - - + + + + + diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/actions.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/actions.js deleted file mode 100644 index f4afb03bbc9..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/actions.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -export const RECEIVE_HOLDERS_SUCCESS = 'permissions/RECEIVE_HOLDERS_SUCCESS'; -export const GRANT_PERMISSION_TO_GROUP = 'permissions/GRANT_PERMISSION_TO_GROUP'; -export const REVOKE_PERMISSION_FROM_GROUP = 'permissions/REVOKE_PERMISSION_FROM_GROUP'; -export const GRANT_PERMISSION_TO_USER = 'permissions/GRANT_PERMISSION_TO_USER'; -export const REVOKE_PERMISSION_TO_USER = 'permissions/REVOKE_PERMISSION_TO_USER'; -export const UPDATE_FILTER = 'permissions/UPDATE_FILTER'; -export const UPDATE_QUERY = 'permissions/UPDATE_QUERY'; -export const SELECT_PERMISSION = 'permissions/SELECT_PERMISSION'; -export const REQUEST_HOLDERS = 'permissions/REQUEST_HOLDERS'; -export const ERROR = 'permissions/ERROR'; - -export const raiseError = message => ({ - type: ERROR, - message -}); diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/error.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/error.js deleted file mode 100644 index 6c060fd8a8b..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/error.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { - RECEIVE_HOLDERS_SUCCESS, - GRANT_PERMISSION_TO_USER, - REVOKE_PERMISSION_TO_USER, - GRANT_PERMISSION_TO_GROUP, - REVOKE_PERMISSION_FROM_GROUP, - ERROR -} from './actions'; - -const error = (state = null, action = {}) => { - switch (action.type) { - case RECEIVE_HOLDERS_SUCCESS: - case GRANT_PERMISSION_TO_USER: - case REVOKE_PERMISSION_TO_USER: - case GRANT_PERMISSION_TO_GROUP: - case REVOKE_PERMISSION_FROM_GROUP: - return null; - case ERROR: - return action.message; - default: - return state; - } -}; - -export default error; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/filter.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/filter.js deleted file mode 100644 index 9c9fcf464b1..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/filter.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { UPDATE_FILTER } from './actions'; - -const filter = (state = 'all', action = {}) => { - if (action.type === UPDATE_FILTER) { - return action.filter; - } - return state; -}; - -export default filter; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/byName.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/byName.js deleted file mode 100644 index f35c9684305..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/byName.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { keyBy } from 'lodash'; -import { - RECEIVE_HOLDERS_SUCCESS, - GRANT_PERMISSION_TO_GROUP, - REVOKE_PERMISSION_FROM_GROUP -} from '../actions'; - -const byName = (state = {}, action = {}) => { - if (action.type === RECEIVE_HOLDERS_SUCCESS) { - const newGroups = keyBy(action.groups, 'name'); - return { ...state, ...newGroups }; - } else if (action.type === GRANT_PERMISSION_TO_GROUP) { - const newGroup = { ...state[action.groupName] }; - newGroup.permissions = [...newGroup.permissions, action.permission]; - return { ...state, [action.groupName]: newGroup }; - } else if (action.type === REVOKE_PERMISSION_FROM_GROUP) { - const newGroup = { ...state[action.groupName] }; - newGroup.permissions = newGroup.permissions.filter(p => p !== action.permission); - return { ...state, [action.groupName]: newGroup }; - } else { - return state; - } -}; - -export default byName; - -export const getGroups = state => state; - -export const getGroupByName = (state, name) => state[name]; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/groups.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/groups.js deleted file mode 100644 index c4e2bd674d7..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/groups.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { combineReducers } from 'redux'; -import byName, { getGroupByName } from './byName'; -import names, { getNames } from './names'; - -export default combineReducers({ - byName, - names -}); - -export const getGroups = state => - getNames(state.names).map(name => getGroupByName(state.byName, name)); diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/names.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/names.js deleted file mode 100644 index 15dc62ccc98..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/groups/names.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { RECEIVE_HOLDERS_SUCCESS } from '../actions'; - -const names = (state = [], action = {}) => { - if (action.type === RECEIVE_HOLDERS_SUCCESS) { - return action.groups.map(group => group.name); - } else { - return state; - } -}; - -export default names; - -export const getNames = state => state; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/loading.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/loading.js deleted file mode 100644 index 2fd5453776c..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/loading.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { REQUEST_HOLDERS, RECEIVE_HOLDERS_SUCCESS } from './actions'; - -const loading = (state = false, action = {}) => { - switch (action.type) { - case REQUEST_HOLDERS: - return true; - case RECEIVE_HOLDERS_SUCCESS: - return false; - default: - return state; - } -}; - -export default loading; - -export const isLoading = state => state; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/query.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/query.js deleted file mode 100644 index 73d60b9ef36..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/query.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { UPDATE_QUERY, REQUEST_HOLDERS } from './actions'; - -const query = (state = '', action = {}) => { - switch (action.type) { - case UPDATE_QUERY: - case REQUEST_HOLDERS: - return action.query; - default: - return state; - } -}; - -export default query; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/rootReducer.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/rootReducer.js deleted file mode 100644 index 856163d9d6c..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/rootReducer.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { combineReducers } from 'redux'; -import users, * as fromUsers from './users/users'; -import groups, * as fromGroups from './groups/groups'; -import loading, * as fromLoading from './loading'; -import query from './query'; -import filter from './filter'; -import selectedPermission from './selectedPermission'; -import error from './error'; - -export default combineReducers({ - users, - groups, - loading, - query, - filter, - selectedPermission, - error -}); - -export const getUsers = state => fromUsers.getUsers(state.users); - -export const getGroups = state => fromGroups.getGroups(state.groups); - -export const isLoading = state => fromLoading.isLoading(state.loading); - -export const getQuery = state => state.query; - -export const getFilter = state => state.filter; - -export const getSelectedPermission = state => state.selectedPermission; - -export const getError = state => state.error; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/selectedPermission.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/selectedPermission.js deleted file mode 100644 index 4a0e4496deb..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/selectedPermission.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { SELECT_PERMISSION } from './actions'; - -const selectedPermission = (state = null, action = {}) => { - if (action.type === SELECT_PERMISSION) { - return action.permission; - } - return state; -}; - -export default selectedPermission; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/users/byLogin.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/users/byLogin.js deleted file mode 100644 index 5e076ffdb80..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/users/byLogin.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { keyBy } from 'lodash'; -import { - RECEIVE_HOLDERS_SUCCESS, - GRANT_PERMISSION_TO_USER, - REVOKE_PERMISSION_TO_USER -} from '../actions'; - -const byLogin = (state = {}, action = {}) => { - if (action.type === RECEIVE_HOLDERS_SUCCESS) { - const newUsers = keyBy(action.users, 'login'); - return { ...state, ...newUsers }; - } else if (action.type === GRANT_PERMISSION_TO_USER) { - const newUser = { ...state[action.login] }; - newUser.permissions = [...newUser.permissions, action.permission]; - return { ...state, [action.login]: newUser }; - } else if (action.type === REVOKE_PERMISSION_TO_USER) { - const newUser = { ...state[action.login] }; - newUser.permissions = newUser.permissions.filter(p => p !== action.permission); - return { ...state, [action.login]: newUser }; - } else { - return state; - } -}; - -export default byLogin; - -export const getUsers = state => state; - -export const getUserByLogin = (state, login) => state[login]; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/users/logins.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/users/logins.js deleted file mode 100644 index 5b66810847c..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/users/logins.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { RECEIVE_HOLDERS_SUCCESS } from '../actions'; - -const logins = (state = [], action = {}) => { - if (action.type === RECEIVE_HOLDERS_SUCCESS) { - return action.users.map(user => user.login); - } else { - return state; - } -}; - -export default logins; - -export const getLogins = state => state; diff --git a/server/sonar-web/src/main/js/apps/permissions/shared/store/users/users.js b/server/sonar-web/src/main/js/apps/permissions/shared/store/users/users.js deleted file mode 100644 index 3abcdf678a6..00000000000 --- a/server/sonar-web/src/main/js/apps/permissions/shared/store/users/users.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2018 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { combineReducers } from 'redux'; -import byLogin, { getUserByLogin } from './byLogin'; -import logins, { getLogins } from './logins'; - -export default combineReducers({ - byLogin, - logins -}); - -export const getUsers = state => - getLogins(state.logins).map(login => getUserByLogin(state.byLogin, login)); diff --git a/server/sonar-web/src/main/js/apps/permissions/styles.css b/server/sonar-web/src/main/js/apps/permissions/styles.css index eae759f0acb..59ba1212b87 100644 --- a/server/sonar-web/src/main/js/apps/permissions/styles.css +++ b/server/sonar-web/src/main/js/apps/permissions/styles.css @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -.permissions-table { -} - .permissions-table > tbody > tr > td { border-bottom: 10px solid #fff !important; } @@ -31,3 +28,11 @@ .permissions-table .permission-column-inner { width: 100px; } + +.permissions-table .divider { + background: #e6e6e6; + border-bottom: 20px solid #fff !important; + border-top: 20px solid #fff !important; + height: 1px; + padding: 0; +} diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx index 7c402913a25..d389c61f514 100644 --- a/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx +++ b/server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx @@ -62,12 +62,12 @@ export default class RestoreAccessModal extends React.PureComponent - grantPermissionToUser( - this.props.project.key, - this.props.currentUser.login, + grantPermissionToUser({ + projectKey: this.props.project.key, + login: this.props.currentUser.login, permission, - this.props.project.organization - ); + organization: this.props.project.organization + }); render() { const header = translate('global_permissions.restore_access'); diff --git a/server/sonar-web/src/main/js/store/rootReducer.ts b/server/sonar-web/src/main/js/store/rootReducer.ts index 6a913690bdb..e46f994f25e 100644 --- a/server/sonar-web/src/main/js/store/rootReducer.ts +++ b/server/sonar-web/src/main/js/store/rootReducer.ts @@ -25,7 +25,6 @@ import metrics, * as fromMetrics from './metrics'; import organizations, * as fromOrganizations from './organizations'; import users, * as fromUsers from './users'; import { AppState, Languages } from '../app/types'; -import permissionsApp, * as fromPermissionsApp from '../apps/permissions/shared/store/rootReducer'; import projectAdminApp, * as fromProjectAdminApp from '../apps/project-admin/store/rootReducer'; import settingsApp, * as fromSettingsApp from '../apps/settings/store/rootReducer'; @@ -38,7 +37,6 @@ export type Store = { users: fromUsers.State; // apps - permissionsApp: any; projectAdminApp: any; settingsApp: any; }; @@ -52,7 +50,6 @@ export default combineReducers({ users, // apps - permissionsApp, projectAdminApp, settingsApp }); @@ -93,34 +90,6 @@ export function areThereCustomOrganizations(state: Store) { return getAppState(state).organizationsEnabled; } -export function getPermissionsAppUsers(state: Store) { - return fromPermissionsApp.getUsers(state.permissionsApp); -} - -export function getPermissionsAppGroups(state: Store) { - return fromPermissionsApp.getGroups(state.permissionsApp); -} - -export function isPermissionsAppLoading(state: Store) { - return fromPermissionsApp.isLoading(state.permissionsApp); -} - -export function getPermissionsAppQuery(state: Store) { - return fromPermissionsApp.getQuery(state.permissionsApp); -} - -export function getPermissionsAppFilter(state: Store) { - return fromPermissionsApp.getFilter(state.permissionsApp); -} - -export function getPermissionsAppSelectedPermission(state: Store) { - return fromPermissionsApp.getSelectedPermission(state.permissionsApp); -} - -export function getPermissionsAppError(state: Store) { - return fromPermissionsApp.getError(state.permissionsApp); -} - export function getGlobalSettingValue(state: Store, key: string) { return fromSettingsApp.getValue(state.settingsApp, key); } -- 2.39.5