@@ -537,25 +537,32 @@ export enum PeriodMode { | |||
PreviousVersion = 'previous_version' | |||
} | |||
export interface Permission { | |||
export interface PermissionDefinition { | |||
key: string; | |||
name: string; | |||
description: string; | |||
} | |||
export type PermissionDefinitions = Array<PermissionDefinition | PermissionDefinitionGroup>; | |||
export interface PermissionDefinitionGroup { | |||
category: string; | |||
permissions: PermissionDefinition[]; | |||
} | |||
export interface PermissionGroup { | |||
description?: string; | |||
id?: string; | |||
name: string; | |||
description?: string; | |||
permissions: string[]; | |||
} | |||
export interface PermissionUser { | |||
avatar?: string; | |||
email?: string; | |||
login: string; | |||
name: string; | |||
email?: string; | |||
permissions: string[]; | |||
avatar?: string; | |||
} | |||
export interface PermissionTemplate { |
@@ -20,12 +20,14 @@ | |||
import React from 'react'; | |||
import PropTypes from 'prop-types'; | |||
import Helmet from 'react-helmet'; | |||
import { debounce } from 'lodash'; | |||
import TemplateHeader from './TemplateHeader'; | |||
import TemplateDetails from './TemplateDetails'; | |||
import HoldersList from '../../permissions/shared/components/HoldersList'; | |||
import SearchForm from '../../permissions/shared/components/SearchForm'; | |||
import { PERMISSIONS_ORDER_FOR_PROJECT } from '../../permissions/project/constants'; | |||
import { | |||
convertToPermissionDefinitions, | |||
PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE | |||
} from '../../permissions/utils'; | |||
import * as api from '../../../api/permissions'; | |||
import { translate } from '../../../helpers/l10n'; | |||
@@ -165,11 +167,10 @@ export default class Template extends React.PureComponent { | |||
}; | |||
render() { | |||
const permissions = PERMISSIONS_ORDER_FOR_PROJECT.map(p => ({ | |||
key: p, | |||
name: translate('projects_role', p), | |||
description: translate('projects_role', p, 'desc') | |||
})); | |||
const permissions = convertToPermissionDefinitions( | |||
PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE, | |||
'projects_role' | |||
); | |||
const allUsers = [...this.state.users]; | |||
@@ -18,36 +18,50 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { connect } from 'react-redux'; | |||
import SearchForm from '../../shared/components/SearchForm'; | |||
import HoldersList from '../../shared/components/HoldersList'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { Organization, Paging, PermissionGroup, PermissionUser } from '../../../../app/types'; | |||
import ListFooter from '../../../../components/controls/ListFooter'; | |||
import { | |||
AppState, | |||
Organization, | |||
Paging, | |||
PermissionGroup, | |||
PermissionUser | |||
} from '../../../../app/types'; | |||
import { | |||
PERMISSIONS_ORDER_GLOBAL, | |||
convertToPermissionDefinitions, | |||
PERMISSIONS_ORDER_GLOBAL_GOV | |||
} from '../../utils'; | |||
import { Store, getAppState } from '../../../../store/rootReducer'; | |||
const PERMISSIONS_ORDER = ['admin', 'profileadmin', 'gateadmin', 'scan', 'provisioning']; | |||
interface StateProps { | |||
appState: Pick<AppState, 'qualifiers'>; | |||
} | |||
interface Props { | |||
interface OwnProps { | |||
filter: string; | |||
grantPermissionToGroup: (groupName: string, permission: string) => Promise<void>; | |||
grantPermissionToUser: (login: string, permission: string) => Promise<void>; | |||
groups: PermissionGroup[]; | |||
groupsPaging: Paging; | |||
groupsPaging?: Paging; | |||
loadHolders: () => void; | |||
loading?: boolean; | |||
onLoadMore: (usersPageIndex: number, groupsPageIndex: number) => void; | |||
onLoadMore: () => void; | |||
onFilter: (filter: string) => void; | |||
onSearch: (query: string) => void; | |||
onSelectPermission: (permission: string) => void; | |||
organization?: Organization; | |||
query: string; | |||
revokePermissionFromGroup: (groupName: string, permission: string) => Promise<void>; | |||
revokePermissionFromUser: (login: string, permission: string) => Promise<void>; | |||
selectedPermission?: string; | |||
users: PermissionUser[]; | |||
usersPaging: Paging; | |||
usersPaging?: Paging; | |||
} | |||
export default class AllHoldersList extends React.PureComponent<Props> { | |||
type Props = StateProps & OwnProps; | |||
export class AllHoldersList extends React.PureComponent<Props> { | |||
handleToggleUser = (user: PermissionUser, permission: string) => { | |||
const hasPermission = user.permissions.includes(permission); | |||
if (hasPermission) { | |||
@@ -67,38 +81,34 @@ export default class AllHoldersList extends React.PureComponent<Props> { | |||
} | |||
}; | |||
handleLoadMore = () => { | |||
this.props.onLoadMore( | |||
this.props.usersPaging.pageIndex + 1, | |||
this.props.groupsPaging.pageIndex + 1 | |||
); | |||
}; | |||
render() { | |||
const { filter, groups, groupsPaging, users, usersPaging } = this.props; | |||
const l10nPrefix = this.props.organization ? 'organizations_permissions' : 'global_permissions'; | |||
const permissions = PERMISSIONS_ORDER.map(p => ({ | |||
key: p, | |||
name: translate(l10nPrefix, p), | |||
description: translate(l10nPrefix, p, 'desc') | |||
})); | |||
const governanceInstalled = this.props.appState.qualifiers.includes('VW'); | |||
const permissions = convertToPermissionDefinitions( | |||
governanceInstalled ? PERMISSIONS_ORDER_GLOBAL_GOV : PERMISSIONS_ORDER_GLOBAL, | |||
l10nPrefix | |||
); | |||
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); | |||
let count = 0; | |||
let total = 0; | |||
if (filter !== 'users') { | |||
count += groups.length; | |||
total += groupsPaging ? groupsPaging.total : groups.length; | |||
} | |||
if (filter !== 'groups') { | |||
count += users.length; | |||
total += usersPaging ? usersPaging.total : users.length; | |||
} | |||
return ( | |||
<> | |||
<HoldersList | |||
groups={this.props.groups} | |||
loading={this.props.loading} | |||
onSelectPermission={this.props.onSelectPermission} | |||
onToggleGroup={this.handleToggleGroup} | |||
onToggleUser={this.handleToggleUser} | |||
permissions={permissions} | |||
selectedPermission={this.props.selectedPermission} | |||
users={this.props.users}> | |||
<SearchForm | |||
filter={this.props.filter} | |||
@@ -107,8 +117,14 @@ export default class AllHoldersList extends React.PureComponent<Props> { | |||
query={this.props.query} | |||
/> | |||
</HoldersList> | |||
<ListFooter count={count} loadMore={this.handleLoadMore} total={total} /> | |||
<ListFooter count={count} loadMore={this.props.onLoadMore} total={total} /> | |||
</> | |||
); | |||
} | |||
} | |||
const mapStateToProps = (state: Store): StateProps => ({ | |||
appState: getAppState(state) | |||
}); | |||
export default connect(mapStateToProps)(AllHoldersList); |
@@ -34,14 +34,13 @@ interface Props { | |||
} | |||
interface State { | |||
filter: string; | |||
filter: 'all' | 'groups' | 'users'; | |||
groups: PermissionGroup[]; | |||
groupsPaging: Paging; | |||
groupsPaging?: Paging; | |||
loading: boolean; | |||
query: string; | |||
selectedPermission?: string; | |||
users: PermissionUser[]; | |||
usersPaging: Paging; | |||
usersPaging?: Paging; | |||
} | |||
export class App extends React.PureComponent<Props, State> { | |||
@@ -52,11 +51,9 @@ export class App extends React.PureComponent<Props, State> { | |||
this.state = { | |||
filter: 'all', | |||
groups: [], | |||
groupsPaging: { pageIndex: 1, pageSize: 100, total: 0 }, | |||
loading: true, | |||
query: '', | |||
users: [], | |||
usersPaging: { pageIndex: 1, pageSize: 100, total: 0 } | |||
users: [] | |||
}; | |||
} | |||
@@ -71,37 +68,25 @@ export class App extends React.PureComponent<Props, State> { | |||
loadUsersAndGroups = (userPage?: number, groupsPage?: number) => { | |||
const { organization } = this.props; | |||
const { filter, query, selectedPermission } = this.state; | |||
const { filter, query } = this.state; | |||
const getUsers = | |||
const getUsers: Promise<{ paging?: Paging; users: PermissionUser[] }> = | |||
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: [] | |||
}); | |||
: Promise.resolve({ paging: undefined, users: [] }); | |||
const getGroups = | |||
const getGroups: Promise<{ paging?: Paging; groups: PermissionGroup[] }> = | |||
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: [] | |||
}); | |||
: Promise.resolve({ paging: undefined, groups: [] }); | |||
return Promise.all([getUsers, getGroups]); | |||
}; | |||
@@ -122,10 +107,11 @@ export class App extends React.PureComponent<Props, State> { | |||
}; | |||
onLoadMore = () => { | |||
const { usersPaging, groupsPaging } = this.state; | |||
this.setState({ loading: true }); | |||
return this.loadUsersAndGroups( | |||
this.state.usersPaging.pageIndex + 1, | |||
this.state.groupsPaging.pageIndex + 1 | |||
usersPaging ? usersPaging.pageIndex + 1 : 1, | |||
groupsPaging ? groupsPaging.pageIndex + 1 : 1 | |||
).then(([usersResponse, groupsResponse]) => { | |||
if (this.mounted) { | |||
this.setState(({ groups, users }) => ({ | |||
@@ -139,7 +125,7 @@ export class App extends React.PureComponent<Props, State> { | |||
}, this.stopLoading); | |||
}; | |||
onFilter = (filter: string) => { | |||
onFilter = (filter: 'all' | 'groups' | 'users') => { | |||
this.setState({ filter }, this.loadHolders); | |||
}; | |||
@@ -147,15 +133,6 @@ export class App extends React.PureComponent<Props, State> { | |||
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 => | |||
@@ -315,11 +292,9 @@ export class App extends React.PureComponent<Props, State> { | |||
onFilter={this.onFilter} | |||
onLoadMore={this.onLoadMore} | |||
onSearch={this.onSearch} | |||
onSelectPermission={this.onSelectPermission} | |||
query={this.state.query} | |||
revokePermissionFromGroup={this.revokePermissionFromGroup} | |||
revokePermissionFromUser={this.revokePermissionFromUser} | |||
selectedPermission={this.state.selectedPermission} | |||
users={this.state.users} | |||
usersPaging={this.state.usersPaging} | |||
/> |
@@ -21,8 +21,8 @@ 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 ListFooter from '../../../../components/controls/ListFooter'; | |||
import { PERMISSIONS_ORDER_BY_QUALIFIER, convertToPermissionDefinitions } from '../../utils'; | |||
import { | |||
Component, | |||
Paging, | |||
@@ -30,7 +30,6 @@ import { | |||
PermissionUser, | |||
Visibility | |||
} from '../../../../app/types'; | |||
import ListFooter from '../../../../components/controls/ListFooter'; | |||
interface Props { | |||
component: Component; | |||
@@ -38,8 +37,8 @@ interface Props { | |||
grantPermissionToGroup: (group: string, permission: string) => Promise<void>; | |||
grantPermissionToUser: (user: string, permission: string) => Promise<void>; | |||
groups: PermissionGroup[]; | |||
groupsPaging: Paging; | |||
onLoadMore: (usersPageIndex: number, groupsPageIndex: number) => void; | |||
groupsPaging?: Paging; | |||
onLoadMore: () => void; | |||
onFilterChange: (filter: string) => void; | |||
onPermissionSelect: (permissions?: string) => void; | |||
onQueryChange: (query: string) => void; | |||
@@ -48,7 +47,7 @@ interface Props { | |||
revokePermissionFromUser: (user: string, permission: string) => Promise<void>; | |||
selectedPermission?: string; | |||
users: PermissionUser[]; | |||
usersPaging: Paging; | |||
usersPaging?: Paging; | |||
visibility?: Visibility; | |||
} | |||
@@ -77,31 +76,24 @@ export default class AllHoldersList extends React.PureComponent<Props> { | |||
this.props.onPermissionSelect(permission); | |||
}; | |||
handleLoadMore = () => { | |||
this.props.onLoadMore( | |||
this.props.usersPaging.pageIndex + 1, | |||
this.props.groupsPaging.pageIndex + 1 | |||
); | |||
}; | |||
render() { | |||
const { filter, groups, groupsPaging, users, usersPaging } = this.props; | |||
let order = PERMISSIONS_ORDER_BY_QUALIFIER[this.props.component.qualifier]; | |||
if (this.props.visibility === Visibility.Public) { | |||
order = without(order, 'user', 'codeviewer'); | |||
} | |||
const permissions = convertToPermissionDefinitions(order, 'projects_role'); | |||
const permissions = order.map(p => ({ | |||
key: p, | |||
name: translate('projects_role', p), | |||
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); | |||
let count = 0; | |||
let total = 0; | |||
if (filter !== 'users') { | |||
count += groups.length; | |||
total += groupsPaging ? groupsPaging.total : groups.length; | |||
} | |||
if (filter !== 'groups') { | |||
count += users.length; | |||
total += usersPaging ? usersPaging.total : users.length; | |||
} | |||
return ( | |||
<> | |||
@@ -120,7 +112,7 @@ export default class AllHoldersList extends React.PureComponent<Props> { | |||
query={this.props.query} | |||
/> | |||
</HoldersList> | |||
<ListFooter count={count} loadMore={this.handleLoadMore} total={total} /> | |||
<ListFooter count={count} loadMore={this.props.onLoadMore} total={total} /> | |||
</> | |||
); | |||
} |
@@ -45,12 +45,12 @@ interface State { | |||
disclaimer: boolean; | |||
filter: string; | |||
groups: PermissionGroup[]; | |||
groupsPaging: Paging; | |||
groupsPaging?: Paging; | |||
loading: boolean; | |||
query: string; | |||
selectedPermission?: string; | |||
users: PermissionUser[]; | |||
usersPaging: Paging; | |||
usersPaging?: Paging; | |||
} | |||
export default class App extends React.PureComponent<Props, State> { | |||
@@ -62,11 +62,9 @@ export default class App extends React.PureComponent<Props, State> { | |||
disclaimer: false, | |||
filter: 'all', | |||
groups: [], | |||
groupsPaging: { pageIndex: 1, pageSize: 100, total: 0 }, | |||
loading: true, | |||
query: '', | |||
users: [], | |||
usersPaging: { pageIndex: 1, pageSize: 100, total: 0 } | |||
users: [] | |||
}; | |||
} | |||
@@ -85,55 +83,67 @@ export default class App extends React.PureComponent<Props, State> { | |||
} | |||
}; | |||
loadHolders = (usersPageIndex?: number, groupsPageIndex?: number) => { | |||
if (this.mounted) { | |||
this.setState({ loading: true }); | |||
const { component } = this.props; | |||
const { filter, query, selectedPermission } = this.state; | |||
const getUsers = | |||
filter !== 'groups' | |||
? 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: [] | |||
}); | |||
loadUsersAndGroups = (userPage?: number, groupsPage?: number) => { | |||
const { component } = this.props; | |||
const { filter, query, selectedPermission } = this.state; | |||
const getUsers: Promise<{ paging?: Paging; users: PermissionUser[] }> = | |||
filter !== 'groups' | |||
? api.getPermissionsUsersForComponent({ | |||
projectKey: component.key, | |||
q: query || undefined, | |||
permission: selectedPermission, | |||
organization: component.organization, | |||
p: userPage | |||
}) | |||
: Promise.resolve({ paging: undefined, users: [] }); | |||
const getGroups: Promise<{ paging?: Paging; groups: PermissionGroup[] }> = | |||
filter !== 'users' | |||
? api.getPermissionsGroupsForComponent({ | |||
projectKey: component.key, | |||
q: query || undefined, | |||
permission: selectedPermission, | |||
organization: component.organization, | |||
p: groupsPage | |||
}) | |||
: Promise.resolve({ paging: undefined, groups: [] }); | |||
return Promise.all([getUsers, getGroups]); | |||
}; | |||
const getGroups = | |||
filter !== 'users' | |||
? 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: [] | |||
}); | |||
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); | |||
}; | |||
Promise.all([getUsers, getGroups]).then(responses => { | |||
if (this.mounted) { | |||
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); | |||
} | |||
onLoadMore = () => { | |||
const { usersPaging, groupsPaging } = this.state; | |||
this.setState({ loading: true }); | |||
return this.loadUsersAndGroups( | |||
usersPaging ? usersPaging.pageIndex + 1 : 1, | |||
groupsPaging ? groupsPaging.pageIndex + 1 : 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); | |||
}; | |||
handleFilterChange = (filter: string) => { | |||
@@ -344,10 +354,6 @@ export default class App extends React.PureComponent<Props, State> { | |||
} | |||
}; | |||
handleLoadMore = (usersPageIndex: number, groupsPageIndex: number) => { | |||
this.loadHolders(usersPageIndex, groupsPageIndex); | |||
}; | |||
render() { | |||
const canTurnToPrivate = | |||
this.props.component.configuration != null && | |||
@@ -389,7 +395,7 @@ export default class App extends React.PureComponent<Props, State> { | |||
groups={this.state.groups} | |||
groupsPaging={this.state.groupsPaging} | |||
onFilterChange={this.handleFilterChange} | |||
onLoadMore={this.handleLoadMore} | |||
onLoadMore={this.onLoadMore} | |||
onPermissionSelect={this.handlePermissionSelect} | |||
onQueryChange={this.handleQueryChange} | |||
query={this.state.query} |
@@ -1,39 +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 PERMISSIONS_ORDER_FOR_PROJECT = [ | |||
'user', | |||
'codeviewer', | |||
'issueadmin', | |||
'securityhotspotadmin', | |||
'admin', | |||
'scan' | |||
]; | |||
export const PERMISSIONS_ORDER_FOR_VIEW = ['user', 'admin']; | |||
export const PERMISSIONS_ORDER_FOR_DEV = ['user', 'admin']; | |||
export const PERMISSIONS_ORDER_BY_QUALIFIER: { [index: string]: string[] } = { | |||
TRK: PERMISSIONS_ORDER_FOR_PROJECT, | |||
VW: PERMISSIONS_ORDER_FOR_VIEW, | |||
SVW: PERMISSIONS_ORDER_FOR_VIEW, | |||
APP: PERMISSIONS_ORDER_FOR_VIEW, | |||
DEV: PERMISSIONS_ORDER_FOR_DEV | |||
}; |
@@ -19,16 +19,16 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { without } from 'lodash'; | |||
import Checkbox from '../../../../components/controls/Checkbox'; | |||
import PermissionCell from './PermissionCell'; | |||
import GroupIcon from '../../../../components/icons-components/GroupIcon'; | |||
import { PermissionGroup } from '../../../../app/types'; | |||
import { PermissionDefinitions, PermissionGroup } from '../../../../app/types'; | |||
import { isPermissionDefinitionGroup } from '../../utils'; | |||
interface Props { | |||
group: PermissionGroup; | |||
permissions: string[]; | |||
selectedPermission?: string; | |||
permissionsOrder: string[]; | |||
onToggle: (group: PermissionGroup, permission: string) => Promise<void>; | |||
permissions: PermissionDefinitions; | |||
selectedPermission?: string; | |||
} | |||
interface State { | |||
@@ -63,26 +63,11 @@ export default class GroupHolder extends React.PureComponent<Props, State> { | |||
}; | |||
render() { | |||
const { selectedPermission } = this.props; | |||
const permissionCells = this.props.permissionsOrder.map(permission => ( | |||
<td | |||
className="text-center text-middle" | |||
key={permission} | |||
style={{ backgroundColor: permission === selectedPermission ? '#d9edf7' : 'transparent' }}> | |||
<Checkbox | |||
checked={this.props.permissions.includes(permission)} | |||
disabled={this.state.loading.includes(permission)} | |||
id={permission} | |||
onCheck={this.handleCheck} | |||
/> | |||
</td> | |||
)); | |||
const { group } = this.props; | |||
return ( | |||
<tr> | |||
<td className="nowrap"> | |||
<td className="nowrap text-middle"> | |||
<div className="display-inline-block text-middle big-spacer-right"> | |||
<GroupIcon /> | |||
</div> | |||
@@ -95,7 +80,16 @@ export default class GroupHolder extends React.PureComponent<Props, State> { | |||
</div> | |||
</div> | |||
</td> | |||
{permissionCells} | |||
{this.props.permissions.map(permission => ( | |||
<PermissionCell | |||
key={isPermissionDefinitionGroup(permission) ? permission.category : permission.key} | |||
loading={this.state.loading} | |||
onCheck={this.handleCheck} | |||
permission={permission} | |||
permissionItem={group} | |||
selectedPermission={this.props.selectedPermission} | |||
/> | |||
))} | |||
</tr> | |||
); | |||
} |
@@ -18,20 +18,21 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { groupBy } from 'lodash'; | |||
import { partition, sortBy } from 'lodash'; | |||
import UserHolder from './UserHolder'; | |||
import GroupHolder from './GroupHolder'; | |||
import PermissionHeader from './PermissionHeader'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { Permission, PermissionGroup, PermissionUser } from '../../../../app/types'; | |||
import { PermissionGroup, PermissionUser, PermissionDefinitions } from '../../../../app/types'; | |||
import { isPermissionDefinitionGroup } from '../../utils'; | |||
interface Props { | |||
loading?: boolean; | |||
groups: PermissionGroup[]; | |||
onSelectPermission: (permission: string) => void; | |||
onSelectPermission?: (permission: string) => void; | |||
onToggleGroup: (group: PermissionGroup, permission: string) => Promise<void>; | |||
onToggleUser: (user: PermissionUser, permission: string) => Promise<void>; | |||
permissions: Permission[]; | |||
permissions: PermissionDefinitions; | |||
selectedPermission?: string; | |||
showPublicProjectsWarning?: boolean; | |||
users: PermissionUser[]; | |||
@@ -42,27 +43,6 @@ export default class HoldersList extends React.PureComponent<Props> { | |||
return (item as PermissionUser).login !== undefined; | |||
} | |||
renderTableHeader() { | |||
const { onSelectPermission, selectedPermission, showPublicProjectsWarning } = this.props; | |||
const cells = this.props.permissions.map(p => ( | |||
<PermissionHeader | |||
key={p.key} | |||
onSelectPermission={onSelectPermission} | |||
permission={p} | |||
selectedPermission={selectedPermission} | |||
showPublicProjectsWarning={showPublicProjectsWarning} | |||
/> | |||
)); | |||
return ( | |||
<thead> | |||
<tr> | |||
<td className="nowrap bordered-bottom">{this.props.children}</td> | |||
{cells} | |||
</tr> | |||
</thead> | |||
); | |||
} | |||
renderEmpty() { | |||
const columns = this.props.permissions.length + 1; | |||
return ( | |||
@@ -72,65 +52,71 @@ export default class HoldersList extends React.PureComponent<Props> { | |||
); | |||
} | |||
renderItem(item: PermissionUser | PermissionGroup, permissionsOrder: string[]) { | |||
return this.isPermissionUser(item) | |||
? this.renderUser(item, permissionsOrder) | |||
: this.renderGroup(item, permissionsOrder); | |||
} | |||
renderUser(user: PermissionUser, permissionsOrder: string[]) { | |||
return ( | |||
renderItem(item: PermissionUser | PermissionGroup, permissions: PermissionDefinitions) { | |||
return this.isPermissionUser(item) ? ( | |||
<UserHolder | |||
key={'user-' + user.login} | |||
key={'user-' + item.login} | |||
onToggle={this.props.onToggleUser} | |||
permissions={user.permissions} | |||
permissionsOrder={permissionsOrder} | |||
permissions={permissions} | |||
selectedPermission={this.props.selectedPermission} | |||
user={user} | |||
user={item} | |||
/> | |||
); | |||
} | |||
renderGroup(group: PermissionGroup, permissionsOrder: string[]) { | |||
return ( | |||
) : ( | |||
<GroupHolder | |||
group={group} | |||
key={'group-' + group.id} | |||
group={item} | |||
key={'group-' + item.id} | |||
onToggle={this.props.onToggleGroup} | |||
permissions={group.permissions} | |||
permissionsOrder={permissionsOrder} | |||
permissions={permissions} | |||
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 { permissions } = this.props; | |||
const items = sortBy([...this.props.users, ...this.props.groups], item => { | |||
if (this.isPermissionUser(item) && item.login === '<creator>') { | |||
return 0; | |||
} | |||
return item.name; | |||
}); | |||
const { true: itemWithPermissions = [], false: itemWithoutPermissions = [] } = groupBy( | |||
const [itemWithPermissions, itemWithoutPermissions] = partition( | |||
items, | |||
item => item.permissions.length > 0 | |||
); | |||
return ( | |||
<div className="boxed-group boxed-group-inner"> | |||
<table className="data zebra permissions-table"> | |||
{this.renderTableHeader()} | |||
<thead> | |||
<tr> | |||
<td className="nowrap bordered-bottom">{this.props.children}</td> | |||
{permissions.map(permission => ( | |||
<PermissionHeader | |||
key={ | |||
isPermissionDefinitionGroup(permission) ? permission.category : permission.key | |||
} | |||
onSelectPermission={this.props.onSelectPermission} | |||
permission={permission} | |||
selectedPermission={this.props.selectedPermission} | |||
showPublicProjectsWarning={this.props.showPublicProjectsWarning} | |||
/> | |||
))} | |||
</tr> | |||
</thead> | |||
<tbody> | |||
{items.length === 0 && !this.props.loading && this.renderEmpty()} | |||
{itemWithPermissions.map(item => this.renderItem(item, permissionsOrder))} | |||
{itemWithPermissions.map(item => this.renderItem(item, permissions))} | |||
{itemWithPermissions.length > 0 && | |||
itemWithoutPermissions.length > 0 && ( | |||
<> | |||
<tr> | |||
<td className="divider" colSpan={6} /> | |||
<td className="divider" colSpan={20} /> | |||
</tr> | |||
<tr /> {/* Keep correct zebra colors in the table */} | |||
<tr /> | |||
{/* Keep correct zebra colors in the table */} | |||
</> | |||
)} | |||
{itemWithoutPermissions.map(item => this.renderItem(item, permissionsOrder))} | |||
{itemWithoutPermissions.map(item => this.renderItem(item, permissions))} | |||
</tbody> | |||
</table> | |||
</div> |
@@ -0,0 +1,74 @@ | |||
/* | |||
* 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 * as classNames from 'classnames'; | |||
import { | |||
PermissionDefinition, | |||
PermissionDefinitionGroup, | |||
PermissionGroup, | |||
PermissionUser | |||
} from '../../../../app/types'; | |||
import { isPermissionDefinitionGroup } from '../../utils'; | |||
import Checkbox from '../../../../components/controls/Checkbox'; | |||
interface Props { | |||
loading: string[]; | |||
onCheck: (checked: boolean, permission?: string) => void; | |||
permission: PermissionDefinition | PermissionDefinitionGroup; | |||
permissionItem: PermissionGroup | PermissionUser; | |||
selectedPermission?: string; | |||
} | |||
export default class PermissionCell extends React.PureComponent<Props> { | |||
render() { | |||
const { loading, onCheck, permission, permissionItem, selectedPermission } = this.props; | |||
if (isPermissionDefinitionGroup(permission)) { | |||
return ( | |||
<td className="text-middle"> | |||
{permission.permissions.map(permission => ( | |||
<div key={permission.key}> | |||
<Checkbox | |||
checked={permissionItem.permissions.includes(permission.key)} | |||
disabled={loading.includes(permission.key)} | |||
id={permission.key} | |||
onCheck={onCheck}> | |||
<span className="little-spacer-left">{permission.name}</span> | |||
</Checkbox> | |||
</div> | |||
))} | |||
</td> | |||
); | |||
} else { | |||
return ( | |||
<td | |||
className={classNames('permission-column text-center text-middle', { | |||
selected: permission.key === selectedPermission | |||
})}> | |||
<Checkbox | |||
checked={permissionItem.permissions.includes(permission.key)} | |||
disabled={loading.includes(permission.key)} | |||
id={permission.key} | |||
onCheck={onCheck} | |||
/> | |||
</td> | |||
); | |||
} | |||
} | |||
} |
@@ -18,15 +18,17 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import * as classNames from 'classnames'; | |||
import HelpTooltip from '../../../../components/controls/HelpTooltip'; | |||
import InstanceMessage from '../../../../components/common/InstanceMessage'; | |||
import Tooltip from '../../../../components/controls/Tooltip'; | |||
import { translate, translateWithParameters } from '../../../../helpers/l10n'; | |||
import { Permission } from '../../../../app/types'; | |||
import { PermissionDefinition, PermissionDefinitionGroup } from '../../../../app/types'; | |||
import { isPermissionDefinitionGroup } from '../../utils'; | |||
import Tooltip from '../../../../components/controls/Tooltip'; | |||
interface Props { | |||
onSelectPermission: (permission: string) => void; | |||
permission: Permission; | |||
onSelectPermission?: (permission: string) => void; | |||
permission: PermissionDefinition | PermissionDefinitionGroup; | |||
selectedPermission?: string; | |||
showPublicProjectsWarning?: boolean; | |||
} | |||
@@ -35,42 +37,68 @@ export default class PermissionHeader extends React.PureComponent<Props> { | |||
handlePermissionClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => { | |||
event.preventDefault(); | |||
event.currentTarget.blur(); | |||
this.props.onSelectPermission(this.props.permission.key); | |||
const { permission } = this.props; | |||
if (this.props.onSelectPermission && !isPermissionDefinitionGroup(permission)) { | |||
this.props.onSelectPermission(permission.key); | |||
} | |||
}; | |||
renderTooltip = (permission: Permission) => { | |||
if (this.props.showPublicProjectsWarning && ['user', 'codeviewer'].includes(permission.key)) { | |||
return ( | |||
<div> | |||
<InstanceMessage message={permission.description} /> | |||
<div className="alert alert-warning spacer-top"> | |||
{translate('projects_role.public_projects_warning')} | |||
getTooltipOverlay = () => { | |||
const { permission } = this.props; | |||
if (isPermissionDefinitionGroup(permission)) { | |||
return permission.permissions.map(permission => ( | |||
<> | |||
<b className="little-spacer-right">{permission.name}:</b> | |||
<InstanceMessage key={permission.key} message={permission.description} /> | |||
<br /> | |||
</> | |||
)); | |||
} else { | |||
if (this.props.showPublicProjectsWarning && ['user', 'codeviewer'].includes(permission.key)) { | |||
return ( | |||
<div> | |||
<InstanceMessage message={permission.description} /> | |||
<div className="alert alert-warning spacer-top"> | |||
{translate('projects_role.public_projects_warning')} | |||
</div> | |||
</div> | |||
</div> | |||
); | |||
); | |||
} | |||
return <InstanceMessage message={permission.description} />; | |||
} | |||
return <InstanceMessage message={permission.description} />; | |||
}; | |||
render() { | |||
const { permission, selectedPermission } = this.props; | |||
const { onSelectPermission, permission } = this.props; | |||
let name; | |||
if (isPermissionDefinitionGroup(permission)) { | |||
name = translate('global_permissions', permission.category); | |||
} else { | |||
name = onSelectPermission ? ( | |||
<Tooltip | |||
overlay={translateWithParameters( | |||
'global_permissions.filter_by_x_permission', | |||
permission.name | |||
)}> | |||
<a href="#" onClick={this.handlePermissionClick}> | |||
{permission.name} | |||
</a> | |||
</Tooltip> | |||
) : ( | |||
permission.name | |||
); | |||
} | |||
return ( | |||
<th | |||
className="permission-column text-center" | |||
style={{ | |||
backgroundColor: permission.key === selectedPermission ? '#d9edf7' : 'transparent' | |||
}}> | |||
className={classNames('permission-column text-center text-middle', { | |||
selected: | |||
!isPermissionDefinitionGroup(permission) && | |||
permission.key === this.props.selectedPermission | |||
})}> | |||
<div className="permission-column-inner"> | |||
<Tooltip | |||
overlay={translateWithParameters( | |||
'global_permissions.filter_by_x_permission', | |||
permission.name | |||
)}> | |||
<a className="text-middle" href="#" onClick={this.handlePermissionClick}> | |||
{permission.name} | |||
</a> | |||
</Tooltip> | |||
<HelpTooltip className="spacer-left" overlay={this.renderTooltip(permission)} /> | |||
{name} | |||
<HelpTooltip className="spacer-left" overlay={this.getTooltipOverlay()} /> | |||
</div> | |||
</th> | |||
); |
@@ -19,17 +19,17 @@ | |||
*/ | |||
import * as React from 'react'; | |||
import { without } from 'lodash'; | |||
import PermissionCell from './PermissionCell'; | |||
import Avatar from '../../../../components/ui/Avatar'; | |||
import Checkbox from '../../../../components/controls/Checkbox'; | |||
import { translate } from '../../../../helpers/l10n'; | |||
import { PermissionUser } from '../../../../app/types'; | |||
import { PermissionDefinitions, PermissionUser } from '../../../../app/types'; | |||
import { isPermissionDefinitionGroup } from '../../utils'; | |||
interface Props { | |||
user: PermissionUser; | |||
permissions: string[]; | |||
selectedPermission?: string; | |||
permissionsOrder: string[]; | |||
onToggle: (user: PermissionUser, permission: string) => Promise<void>; | |||
permissions: PermissionDefinitions; | |||
selectedPermission?: string; | |||
user: PermissionUser; | |||
} | |||
interface State { | |||
@@ -64,26 +64,22 @@ export default class UserHolder extends React.PureComponent<Props, State> { | |||
}; | |||
render() { | |||
const { selectedPermission } = this.props; | |||
const permissionCells = this.props.permissionsOrder.map(permission => ( | |||
<td | |||
className="text-center text-middle" | |||
key={permission} | |||
style={{ backgroundColor: permission === selectedPermission ? '#d9edf7' : 'transparent' }}> | |||
<Checkbox | |||
checked={this.props.permissions.includes(permission)} | |||
disabled={this.state.loading.includes(permission)} | |||
id={permission} | |||
onCheck={this.handleCheck} | |||
/> | |||
</td> | |||
const { user } = this.props; | |||
const permissionCells = this.props.permissions.map(permission => ( | |||
<PermissionCell | |||
key={isPermissionDefinitionGroup(permission) ? permission.category : permission.key} | |||
loading={this.state.loading} | |||
onCheck={this.handleCheck} | |||
permission={permission} | |||
permissionItem={user} | |||
selectedPermission={this.props.selectedPermission} | |||
/> | |||
)); | |||
const { user } = this.props; | |||
if (user.login === '<creator>') { | |||
return ( | |||
<tr> | |||
<td className="nowrap"> | |||
<td className="nowrap text-middle"> | |||
<div className="display-inline-block text-middle"> | |||
<div> | |||
<strong>{user.name}</strong> | |||
@@ -100,7 +96,7 @@ export default class UserHolder extends React.PureComponent<Props, State> { | |||
return ( | |||
<tr> | |||
<td className="nowrap"> | |||
<td className="nowrap text-middle"> | |||
<Avatar | |||
className="text-middle big-spacer-right" | |||
hash={user.avatar} |
@@ -33,29 +33,35 @@ const groupHolder = ( | |||
group={group} | |||
key="foo" | |||
onToggle={jest.fn(() => Promise.resolve())} | |||
permissions={['bar']} | |||
permissionsOrder={['bar', 'baz']} | |||
permissions={[ | |||
{ | |||
category: 'admin', | |||
permissions: [ | |||
{ key: 'foo', name: 'Foo', description: '' }, | |||
{ key: 'bar', name: 'Bar', description: '' } | |||
] | |||
}, | |||
{ key: 'baz', name: 'Baz', description: '' } | |||
]} | |||
selectedPermission={'bar'} | |||
/> | |||
); | |||
it('should display checkboxes for permissions', () => { | |||
it('should render correctly', () => { | |||
expect(shallow(groupHolder)).toMatchSnapshot(); | |||
}); | |||
it('should disabled checkboxes when waiting for promise to return', async () => { | |||
it('should disabled PermissionCell checkboxes when waiting for promise to return', async () => { | |||
const wrapper = shallow(groupHolder); | |||
expect(wrapper.state().loading).toEqual([]); | |||
(wrapper.instance() as GroupHolder).handleCheck(true, 'baz'); | |||
wrapper.update(); | |||
expect(wrapper.state().loading).toEqual(['baz']); | |||
expect(wrapper).toMatchSnapshot(); | |||
(wrapper.instance() as GroupHolder).handleCheck(true, 'bar'); | |||
wrapper.update(); | |||
expect(wrapper.state().loading).toEqual(['baz', 'bar']); | |||
expect(wrapper).toMatchSnapshot(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().loading).toEqual([]); |
@@ -21,7 +21,16 @@ import * as React from 'react'; | |||
import { shallow } from 'enzyme'; | |||
import HoldersList from '../HoldersList'; | |||
const permissions = [{ key: 'bar', name: 'bar', description: 'foo' }]; | |||
const permissions = [ | |||
{ key: 'foo', name: 'Foo', description: '' }, | |||
{ | |||
category: 'admin', | |||
permissions: [ | |||
{ key: 'bar', name: 'Bar', description: '' }, | |||
{ key: 'baz', name: 'Baz', description: '' } | |||
] | |||
} | |||
]; | |||
const groups = [ | |||
{ id: 'foobar', name: 'Foobar', permissions: ['bar'] }, |
@@ -0,0 +1,89 @@ | |||
/* | |||
* 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 { shallow } from 'enzyme'; | |||
import PermissionCell from '../PermissionCell'; | |||
const permissionItem = { | |||
id: 'foobar', | |||
name: 'Foobar', | |||
permissions: ['bar'] | |||
}; | |||
const permission = { key: 'baz', name: 'Baz', description: '' }; | |||
const permissionGroup = { | |||
category: 'admin', | |||
permissions: [ | |||
{ key: 'foo', name: 'Foo', description: '' }, | |||
{ key: 'bar', name: 'Bar', description: '' } | |||
] | |||
}; | |||
it('should display unchecked checkbox', () => { | |||
expect( | |||
shallow( | |||
<PermissionCell | |||
loading={[]} | |||
onCheck={jest.fn()} | |||
permission={permission} | |||
permissionItem={permissionItem} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should display multiple checkboxes with one checked', () => { | |||
expect( | |||
shallow( | |||
<PermissionCell | |||
loading={[]} | |||
onCheck={jest.fn()} | |||
permission={permissionGroup} | |||
permissionItem={permissionItem} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should display disabled checkbox', () => { | |||
expect( | |||
shallow( | |||
<PermissionCell | |||
loading={['baz']} | |||
onCheck={jest.fn()} | |||
permission={permission} | |||
permissionItem={permissionItem} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should display selected checkbox', () => { | |||
expect( | |||
shallow( | |||
<PermissionCell | |||
loading={[]} | |||
onCheck={jest.fn()} | |||
permission={permission} | |||
permissionItem={permissionItem} | |||
selectedPermission="baz" | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -32,30 +32,36 @@ const userHolder = ( | |||
<UserHolder | |||
key="foo" | |||
onToggle={jest.fn(() => Promise.resolve())} | |||
permissions={['bar']} | |||
permissionsOrder={['bar', 'baz']} | |||
permissions={[ | |||
{ | |||
category: 'admin', | |||
permissions: [ | |||
{ key: 'foo', name: 'Foo', description: '' }, | |||
{ key: 'bar', name: 'Bar', description: '' } | |||
] | |||
}, | |||
{ key: 'baz', name: 'Baz', description: '' } | |||
]} | |||
selectedPermission={'bar'} | |||
user={user} | |||
/> | |||
); | |||
it('should display checkboxes for permissions', () => { | |||
it('should render correctly', () => { | |||
expect(shallow(userHolder)).toMatchSnapshot(); | |||
}); | |||
it('should disabled checkboxes when waiting for promise to return', async () => { | |||
it('should disabled PermissionCell checkboxes when waiting for promise to return', async () => { | |||
const wrapper = shallow(userHolder); | |||
expect(wrapper.state().loading).toEqual([]); | |||
(wrapper.instance() as UserHolder).handleCheck(true, 'baz'); | |||
wrapper.update(); | |||
expect(wrapper.state().loading).toEqual(['baz']); | |||
expect(wrapper).toMatchSnapshot(); | |||
(wrapper.instance() as UserHolder).handleCheck(true, 'bar'); | |||
wrapper.update(); | |||
expect(wrapper.state().loading).toEqual(['baz', 'bar']); | |||
expect(wrapper).toMatchSnapshot(); | |||
await waitAndUpdate(wrapper); | |||
expect(wrapper.state().loading).toEqual([]); |
@@ -1,9 +1,9 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should disabled checkboxes when waiting for promise to return 1`] = ` | |||
exports[`should render correctly 1`] = ` | |||
<tr> | |||
<td | |||
className="nowrap" | |||
className="nowrap text-middle" | |||
> | |||
<div | |||
className="display-inline-block text-middle big-spacer-right" | |||
@@ -28,169 +28,59 @@ exports[`should disabled checkboxes when waiting for promise to return 1`] = ` | |||
/> | |||
</div> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="bar" | |||
style={ | |||
<PermissionCell | |||
key="admin" | |||
loading={Array []} | |||
onCheck={[Function]} | |||
permission={ | |||
Object { | |||
"backgroundColor": "#d9edf7", | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={true} | |||
disabled={false} | |||
id="bar" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="baz" | |||
style={ | |||
Object { | |||
"backgroundColor": "transparent", | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={true} | |||
id="baz" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should disabled checkboxes when waiting for promise to return 2`] = ` | |||
<tr> | |||
<td | |||
className="nowrap" | |||
> | |||
<div | |||
className="display-inline-block text-middle big-spacer-right" | |||
> | |||
<GroupIcon /> | |||
</div> | |||
<div | |||
className="display-inline-block text-middle" | |||
> | |||
<div> | |||
<strong> | |||
Foobar | |||
</strong> | |||
</div> | |||
<div | |||
className="little-spacer-top" | |||
style={ | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"whiteSpace": "normal", | |||
} | |||
} | |||
/> | |||
</div> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="bar" | |||
style={ | |||
Object { | |||
"backgroundColor": "#d9edf7", | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={true} | |||
disabled={true} | |||
id="bar" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="baz" | |||
style={ | |||
permissionItem={ | |||
Object { | |||
"backgroundColor": "transparent", | |||
"id": "foobar", | |||
"name": "Foobar", | |||
"permissions": Array [ | |||
"bar", | |||
], | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={true} | |||
id="baz" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should display checkboxes for permissions 1`] = ` | |||
<tr> | |||
<td | |||
className="nowrap" | |||
> | |||
<div | |||
className="display-inline-block text-middle big-spacer-right" | |||
> | |||
<GroupIcon /> | |||
</div> | |||
<div | |||
className="display-inline-block text-middle" | |||
> | |||
<div> | |||
<strong> | |||
Foobar | |||
</strong> | |||
</div> | |||
<div | |||
className="little-spacer-top" | |||
style={ | |||
Object { | |||
"whiteSpace": "normal", | |||
} | |||
} | |||
/> | |||
</div> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="bar" | |||
style={ | |||
selectedPermission="bar" | |||
/> | |||
<PermissionCell | |||
key="baz" | |||
loading={Array []} | |||
onCheck={[Function]} | |||
permission={ | |||
Object { | |||
"backgroundColor": "#d9edf7", | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={true} | |||
disabled={false} | |||
id="bar" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="baz" | |||
style={ | |||
permissionItem={ | |||
Object { | |||
"backgroundColor": "transparent", | |||
"id": "foobar", | |||
"name": "Foobar", | |||
"permissions": Array [ | |||
"bar", | |||
], | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={false} | |||
id="baz" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
selectedPermission="bar" | |||
/> | |||
</tr> | |||
`; |
@@ -13,13 +13,35 @@ exports[`should display users and groups 1`] = ` | |||
className="nowrap bordered-bottom" | |||
/> | |||
<PermissionHeader | |||
key="bar" | |||
key="foo" | |||
onSelectPermission={[MockFunction]} | |||
permission={ | |||
Object { | |||
"description": "foo", | |||
"key": "bar", | |||
"name": "bar", | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
} | |||
} | |||
selectedPermission="bar" | |||
/> | |||
<PermissionHeader | |||
key="admin" | |||
onSelectPermission={[MockFunction]} | |||
permission={ | |||
Object { | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
}, | |||
], | |||
} | |||
} | |||
selectedPermission="bar" | |||
@@ -27,41 +49,31 @@ exports[`should display users and groups 1`] = ` | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<GroupHolder | |||
group={ | |||
Object { | |||
"id": "barbaz", | |||
"name": "Barbaz", | |||
"permissions": Array [ | |||
"bar", | |||
], | |||
} | |||
} | |||
key="group-barbaz" | |||
onToggle={[MockFunction]} | |||
permissions={ | |||
Array [ | |||
"bar", | |||
] | |||
} | |||
permissionsOrder={ | |||
Array [ | |||
"bar", | |||
] | |||
} | |||
selectedPermission="bar" | |||
/> | |||
<UserHolder | |||
key="user-barbaz" | |||
onToggle={[MockFunction]} | |||
permissions={ | |||
Array [ | |||
"bar", | |||
] | |||
} | |||
permissionsOrder={ | |||
Array [ | |||
"bar", | |||
Object { | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
}, | |||
], | |||
}, | |||
] | |||
} | |||
selectedPermission="bar" | |||
@@ -78,23 +90,37 @@ exports[`should display users and groups 1`] = ` | |||
<GroupHolder | |||
group={ | |||
Object { | |||
"id": "foobar", | |||
"name": "Foobar", | |||
"id": "barbaz", | |||
"name": "Barbaz", | |||
"permissions": Array [ | |||
"bar", | |||
], | |||
} | |||
} | |||
key="group-foobar" | |||
key="group-barbaz" | |||
onToggle={[MockFunction]} | |||
permissions={ | |||
Array [ | |||
"bar", | |||
] | |||
} | |||
permissionsOrder={ | |||
Array [ | |||
"bar", | |||
Object { | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
}, | |||
], | |||
}, | |||
] | |||
} | |||
selectedPermission="bar" | |||
@@ -104,12 +130,26 @@ exports[`should display users and groups 1`] = ` | |||
onToggle={[MockFunction]} | |||
permissions={ | |||
Array [ | |||
"bar", | |||
] | |||
} | |||
permissionsOrder={ | |||
Array [ | |||
"bar", | |||
Object { | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
}, | |||
], | |||
}, | |||
] | |||
} | |||
selectedPermission="bar" | |||
@@ -123,12 +163,53 @@ exports[`should display users and groups 1`] = ` | |||
} | |||
} | |||
/> | |||
<tr> | |||
<td | |||
className="divider" | |||
colSpan={6} | |||
/> | |||
</tr> | |||
<GroupHolder | |||
group={ | |||
Object { | |||
"id": "foobar", | |||
"name": "Foobar", | |||
"permissions": Array [ | |||
"bar", | |||
], | |||
} | |||
} | |||
key="group-foobar" | |||
onToggle={[MockFunction]} | |||
permissions={ | |||
Array [ | |||
Object { | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
}, | |||
], | |||
}, | |||
] | |||
} | |||
selectedPermission="bar" | |||
/> | |||
<React.Fragment> | |||
<tr> | |||
<td | |||
className="divider" | |||
colSpan={20} | |||
/> | |||
</tr> | |||
<tr /> | |||
</React.Fragment> | |||
<GroupHolder | |||
group={ | |||
Object { | |||
@@ -139,10 +220,28 @@ exports[`should display users and groups 1`] = ` | |||
} | |||
key="group-abc" | |||
onToggle={[MockFunction]} | |||
permissions={Array []} | |||
permissionsOrder={ | |||
permissions={ | |||
Array [ | |||
"bar", | |||
Object { | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
}, | |||
], | |||
}, | |||
] | |||
} | |||
selectedPermission="bar" | |||
@@ -150,10 +249,28 @@ exports[`should display users and groups 1`] = ` | |||
<UserHolder | |||
key="user-bcd" | |||
onToggle={[MockFunction]} | |||
permissions={Array []} | |||
permissionsOrder={ | |||
permissions={ | |||
Array [ | |||
"bar", | |||
Object { | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
}, | |||
], | |||
}, | |||
] | |||
} | |||
selectedPermission="bar" |
@@ -0,0 +1,84 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display disabled checkbox 1`] = ` | |||
<td | |||
className="permission-column text-center text-middle" | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={true} | |||
id="baz" | |||
onCheck={[MockFunction]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
`; | |||
exports[`should display multiple checkboxes with one checked 1`] = ` | |||
<td | |||
className="text-middle" | |||
> | |||
<div | |||
key="foo" | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={false} | |||
id="foo" | |||
onCheck={[MockFunction]} | |||
thirdState={false} | |||
> | |||
<span | |||
className="little-spacer-left" | |||
> | |||
Foo | |||
</span> | |||
</Checkbox> | |||
</div> | |||
<div | |||
key="bar" | |||
> | |||
<Checkbox | |||
checked={true} | |||
disabled={false} | |||
id="bar" | |||
onCheck={[MockFunction]} | |||
thirdState={false} | |||
> | |||
<span | |||
className="little-spacer-left" | |||
> | |||
Bar | |||
</span> | |||
</Checkbox> | |||
</div> | |||
</td> | |||
`; | |||
exports[`should display selected checkbox 1`] = ` | |||
<td | |||
className="permission-column text-center text-middle selected" | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={false} | |||
id="baz" | |||
onCheck={[MockFunction]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
`; | |||
exports[`should display unchecked checkbox 1`] = ` | |||
<td | |||
className="permission-column text-center text-middle" | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={false} | |||
id="baz" | |||
onCheck={[MockFunction]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
`; |
@@ -1,9 +1,9 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should disabled checkboxes when waiting for promise to return 1`] = ` | |||
exports[`should render correctly 1`] = ` | |||
<tr> | |||
<td | |||
className="nowrap" | |||
className="nowrap text-middle" | |||
> | |||
<Connect(Avatar) | |||
className="text-middle big-spacer-right" | |||
@@ -28,169 +28,59 @@ exports[`should disabled checkboxes when waiting for promise to return 1`] = ` | |||
/> | |||
</div> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="bar" | |||
style={ | |||
<PermissionCell | |||
key="admin" | |||
loading={Array []} | |||
onCheck={[Function]} | |||
permission={ | |||
Object { | |||
"backgroundColor": "#d9edf7", | |||
"category": "admin", | |||
"permissions": Array [ | |||
Object { | |||
"description": "", | |||
"key": "foo", | |||
"name": "Foo", | |||
}, | |||
Object { | |||
"description": "", | |||
"key": "bar", | |||
"name": "Bar", | |||
}, | |||
], | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={true} | |||
disabled={false} | |||
id="bar" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="baz" | |||
style={ | |||
permissionItem={ | |||
Object { | |||
"backgroundColor": "transparent", | |||
"login": "john doe", | |||
"name": "John Doe", | |||
"permissions": Array [ | |||
"bar", | |||
], | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={true} | |||
id="baz" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should disabled checkboxes when waiting for promise to return 2`] = ` | |||
<tr> | |||
<td | |||
className="nowrap" | |||
> | |||
<Connect(Avatar) | |||
className="text-middle big-spacer-right" | |||
name="John Doe" | |||
size={36} | |||
/> | |||
<div | |||
className="display-inline-block text-middle" | |||
> | |||
<div> | |||
<strong> | |||
John Doe | |||
</strong> | |||
<span | |||
className="note spacer-left" | |||
> | |||
john doe | |||
</span> | |||
</div> | |||
<div | |||
className="little-spacer-top" | |||
/> | |||
</div> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="bar" | |||
style={ | |||
Object { | |||
"backgroundColor": "#d9edf7", | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={true} | |||
disabled={true} | |||
id="bar" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
selectedPermission="bar" | |||
/> | |||
<PermissionCell | |||
key="baz" | |||
style={ | |||
loading={Array []} | |||
onCheck={[Function]} | |||
permission={ | |||
Object { | |||
"backgroundColor": "transparent", | |||
"description": "", | |||
"key": "baz", | |||
"name": "Baz", | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={true} | |||
id="baz" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should display checkboxes for permissions 1`] = ` | |||
<tr> | |||
<td | |||
className="nowrap" | |||
> | |||
<Connect(Avatar) | |||
className="text-middle big-spacer-right" | |||
name="John Doe" | |||
size={36} | |||
/> | |||
<div | |||
className="display-inline-block text-middle" | |||
> | |||
<div> | |||
<strong> | |||
John Doe | |||
</strong> | |||
<span | |||
className="note spacer-left" | |||
> | |||
john doe | |||
</span> | |||
</div> | |||
<div | |||
className="little-spacer-top" | |||
/> | |||
</div> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="bar" | |||
style={ | |||
permissionItem={ | |||
Object { | |||
"backgroundColor": "#d9edf7", | |||
"login": "john doe", | |||
"name": "John Doe", | |||
"permissions": Array [ | |||
"bar", | |||
], | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={true} | |||
disabled={false} | |||
id="bar" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
<td | |||
className="text-center text-middle" | |||
key="baz" | |||
style={ | |||
Object { | |||
"backgroundColor": "transparent", | |||
} | |||
} | |||
> | |||
<Checkbox | |||
checked={false} | |||
disabled={false} | |||
id="baz" | |||
onCheck={[Function]} | |||
thirdState={false} | |||
/> | |||
</td> | |||
selectedPermission="bar" | |||
/> | |||
</tr> | |||
`; |
@@ -17,22 +17,24 @@ | |||
* 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 > tbody > tr > td { | |||
border-bottom: 10px solid #fff !important; | |||
} | |||
.permissions-table .permission-column { | |||
width: 1px; | |||
.permissions-table .permission-column.selected { | |||
background-color: #d9edf7; | |||
} | |||
.permissions-table .permission-column-inner { | |||
display: inline-block; | |||
width: 100px; | |||
} | |||
.permissions-table .divider { | |||
background: #e6e6e6; | |||
border-bottom: 20px solid #fff !important; | |||
border-top: 20px solid #fff !important; | |||
background: #fff; | |||
padding: 16px 0; | |||
} | |||
.permissions-table .divider::after { | |||
display: block; | |||
content: ''; | |||
background: var(--barBorderColor); | |||
height: 1px; | |||
padding: 0; | |||
width: 100%; | |||
} |
@@ -0,0 +1,87 @@ | |||
/* | |||
* 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 { translate } from '../../helpers/l10n'; | |||
import { PermissionDefinition, PermissionDefinitionGroup } from '../../app/types'; | |||
export const PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE = [ | |||
'user', | |||
'codeviewer', | |||
'issueadmin', | |||
'securityhotspotadmin', | |||
'admin', | |||
'scan' | |||
]; | |||
export const PERMISSIONS_ORDER_GLOBAL = [ | |||
'admin', | |||
{ category: 'administer', permissions: ['gateadmin', 'profileadmin'] }, | |||
'scan', | |||
{ category: 'creator', permissions: ['provisioning'] } | |||
]; | |||
export const PERMISSIONS_ORDER_GLOBAL_GOV = [ | |||
'admin', | |||
{ category: 'administer', permissions: ['gateadmin', 'profileadmin'] }, | |||
'scan', | |||
{ category: 'creator', permissions: ['provisioning', 'applicationcreator', 'portfoliocreator'] } | |||
]; | |||
export const PERMISSIONS_ORDER_FOR_VIEW = ['user', 'admin']; | |||
export const PERMISSIONS_ORDER_FOR_DEV = ['user', 'admin']; | |||
export const PERMISSIONS_ORDER_BY_QUALIFIER: { [index: string]: string[] } = { | |||
TRK: PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE, | |||
VW: PERMISSIONS_ORDER_FOR_VIEW, | |||
SVW: PERMISSIONS_ORDER_FOR_VIEW, | |||
APP: PERMISSIONS_ORDER_FOR_VIEW, | |||
DEV: PERMISSIONS_ORDER_FOR_DEV | |||
}; | |||
function convertToPermissionDefinition(permission: string, l10nPrefix: string) { | |||
return { | |||
key: permission, | |||
name: translate(l10nPrefix, permission), | |||
description: translate(l10nPrefix, permission, 'desc') | |||
}; | |||
} | |||
export function convertToPermissionDefinitions( | |||
permissions: Array<string | { category: string; permissions: string[] }>, | |||
l10nPrefix: string | |||
): Array<PermissionDefinition | PermissionDefinitionGroup> { | |||
return permissions.map(permission => { | |||
if (typeof permission === 'object') { | |||
return { | |||
category: permission.category, | |||
permissions: permission.permissions.map(permission => | |||
convertToPermissionDefinition(permission, l10nPrefix) | |||
) | |||
}; | |||
} | |||
return convertToPermissionDefinition(permission, l10nPrefix); | |||
}); | |||
} | |||
export function isPermissionDefinitionGroup( | |||
permission?: PermissionDefinition | PermissionDefinitionGroup | |||
): permission is PermissionDefinitionGroup { | |||
return Boolean(permission && (permission as PermissionDefinitionGroup).category); | |||
} |
@@ -1998,21 +1998,25 @@ metric.wont_fix_issues.name=Won't Fix Issues | |||
global_permissions.permission=Permission | |||
global_permissions.users=Users | |||
global_permissions.groups=Groups | |||
global_permissions.administer=Administer | |||
global_permissions.creator=Create | |||
global_permissions.admin=Administer System | |||
global_permissions.admin.desc=Ability to perform all administration functions for the instance. | |||
global_permissions.profileadmin=Administer Quality Profiles | |||
global_permissions.profileadmin=Quality Profiles | |||
global_permissions.profileadmin.desc=Ability to perform any action on quality profiles. | |||
global_permissions.gateadmin=Administer Quality Gates | |||
global_permissions.gateadmin=Quality Gates | |||
global_permissions.gateadmin.desc=Ability to perform any action on quality gates. | |||
global_permissions.scan=Execute Analysis | |||
global_permissions.scan.desc=Ability to get all settings required to perform an analysis (including the secured settings like passwords) and to push analysis results to the {instance} server. | |||
global_permissions.provisioning=Create Projects | |||
global_permissions.provisioning=Projects | |||
global_permissions.provisioning.desc=Ability to initialize a project so its settings can be configured before the first analysis. | |||
global_permissions.filter_by_x_permission=Filter by "{0}" permission | |||
global_permissions.restore_access=Restore Access | |||
global_permissions.restore_access.message=You will receive {browse} and {administer} permissions on the project. Do you want to continue? | |||
global_permissions.applicationcreator=Applications | |||
global_permissions.applicationcreator.desc=Ability to create an application. | |||
global_permissions.portfoliocreator=Portfolios | |||
global_permissions.portfoliocreator.desc=Ability to create a portfolio. | |||
#------------------------------------------------------------------------------ | |||
# |