'build-app:computation'
'build-app:custom-measures'
'build-app:drilldown'
+ 'build-app:global-permissions'
'build-app:groups'
'build-app:issues'
'build-app:maintenance'
'<%= BUILD_PATH %>/js/apps/custom-measures/templates.js': [
'<%= SOURCE_PATH %>/js/apps/custom-measures/templates/**/*.hbs'
]
+ '<%= BUILD_PATH %>/js/apps/global-permissions/templates.js': [
+ '<%= SOURCE_PATH %>/js/apps/global-permissions/templates/**/*.hbs'
+ ]
clean:
--- /dev/null
+import React from 'react';
+import Main from './main';
+
+const $ = jQuery;
+
+export default {
+ start(options) {
+ window.requestMessages().done(() => {
+ var el = document.querySelector(options.el);
+ React.render(<Main/>, el);
+ });
+ }
+};
--- /dev/null
+define([
+ 'components/common/modals',
+ 'components/common/select-list',
+ './templates'
+], function (Modal) {
+
+ return Modal.extend({
+ template: Templates['global-permissions-groups'],
+
+ onRender: function () {
+ this._super();
+ new window.SelectList({
+ el: this.$('#global-permissions-groups'),
+ width: '100%',
+ readOnly: false,
+ focusSearch: false,
+ format: function (item) {
+ return item.name;
+ },
+ queryParam: 'q',
+ searchUrl: baseUrl + '/api/permissions/groups?ps=100&permission=' + this.options.permission,
+ selectUrl: baseUrl + '/api/permissions/add_group',
+ deselectUrl: baseUrl + '/api/permissions/remove_group',
+ extra: {
+ permission: this.options.permission
+ },
+ selectParameter: 'groupId',
+ selectParameterValue: 'id',
+ parse: function (r) {
+ this.more = false;
+ return r.groups;
+ }
+ });
+ },
+
+ onDestroy: function () {
+ this.options.refresh();
+ this._super();
+ }
+ });
+
+});
--- /dev/null
+import React from 'react';
+import PermissionsList from './permissions-list';
+
+let $ = jQuery;
+
+export default React.createClass({
+ getInitialState() {
+ return { permissions: [] };
+ },
+
+ componentDidMount() {
+ this.requestPermissions();
+ },
+
+ requestPermissions() {
+ const url = `${window.baseUrl}/api/permissions/search_global_permissions`;
+ $.get(url).done(r => {
+ this.setState({ permissions: r.globalPermissions });
+ });
+ },
+
+ render() {
+ return (
+ <div className="page">
+ <header id="global-permissions-header" className="page-header">
+ <h1 className="page-title">{window.t('global_permissions.page')}</h1>
+ <p className="page-description">{window.t('global_permissions.page.description')}</p>
+ </header>
+ <PermissionsList permissions={this.state.permissions}/>
+ </div>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import PermissionsUsersGroupsMixin from './permission-users-groups-mixin';
+import GroupsView from './groups-view';
+
+export default React.createClass({
+ mixins: [PermissionsUsersGroupsMixin],
+
+ renderUpdateLink() {
+ return (
+ <a onClick={this.updateGroups}
+ className="icon-bullet-list"
+ title="Update Groups"
+ data-toggle="tooltip"
+ href="#"></a>
+ );
+ },
+
+ renderItem(item) {
+ return item.name;
+ },
+
+ renderTitle() {
+ console.log(this.props);
+ return 'Groups';
+ },
+
+ updateGroups(e) {
+ e.preventDefault();
+ new GroupsView({
+ permission: this.props.permission.key,
+ refresh: this.props.refresh
+ }).render();
+ }
+});
--- /dev/null
+import React from 'react';
+
+let $ = jQuery;
+
+export default {
+ propTypes: {
+ permission: React.PropTypes.object.isRequired,
+ max: React.PropTypes.number.isRequired,
+ items: React.PropTypes.array,
+ total: React.PropTypes.number,
+ refresh: React.PropTypes.func.isRequired
+ },
+
+ renderNotDisplayed() {
+ const notDisplayedCount = this.props.total - this.props.max;
+ return notDisplayedCount > 0 ? <span className="note spacer-right" href="#">and {notDisplayedCount} more</span> : null;
+ },
+
+ renderItems() {
+ const displayed = this.props.items.map(item => {
+ return <li key={item.name} className="spacer-left little-spacer-bottom">{this.renderItem(item)}</li>;
+ });
+ return (
+ <ul className="overflow-hidden bordered-left">
+ {displayed}
+ <li className="spacer-left little-spacer-bottom">
+ {this.renderNotDisplayed()}
+ {this.renderUpdateLink()}
+ </li>
+ </ul>
+ );
+ },
+
+ renderCount() {
+ return (
+ <ul className="overflow-hidden bordered-left">
+ <li className="spacer-left little-spacer-bottom">
+ <span className="spacer-right">{this.props.total}</span>
+ {this.renderUpdateLink()}
+ </li>
+ </ul>
+ );
+ },
+
+ render() {
+ return (
+ <li className="abs-width-400">
+ <div className="pull-left spacer-right">
+ <strong>{this.renderTitle()}</strong>
+ </div>
+ {this.props.items ? this.renderItems() : this.renderCount()}
+ </li>
+ );
+ }
+};
--- /dev/null
+import React from 'react';
+import PermissionsUsersGroupsMixin from './permission-users-groups-mixin';
+import Avatar from 'components/shared/avatar';
+import UsersView from './users-view';
+
+export default React.createClass({
+ mixins: [PermissionsUsersGroupsMixin],
+
+ renderUpdateLink() {
+ return (
+ <a onClick={this.updateUsers}
+ className="icon-bullet-list"
+ title="Update Users"
+ data-toggle="tooltip"
+ href="#"></a>
+ );
+ },
+
+ renderItem(item) {
+ return item.name;
+ },
+
+ renderTitle() {
+ return 'Users';
+ },
+
+ updateUsers(e) {
+ e.preventDefault();
+ new UsersView({
+ permission: this.props.permission.key,
+ refresh: this.props.refresh
+ }).render();
+ }
+});
--- /dev/null
+import React from 'react';
+import PermissionUsers from './permission-users';
+import PermissionGroups from './permission-groups';
+
+let $ = jQuery;
+
+// Maximum number of displayed groups
+const MAX_ITEMS = 3;
+
+export default React.createClass({
+ propTypes: {
+ permission: React.PropTypes.object.isRequired
+ },
+
+ getInitialState() {
+ return {};
+ },
+
+ componentDidMount() {
+ this.requestUsers();
+ this.requestGroups();
+ },
+
+ requestUsers() {
+ const url = `${window.baseUrl}/api/permissions/users`;
+ const data = { permission: this.props.permission.key, ps: MAX_ITEMS };
+ $.get(url, data).done(r => this.setState({ users: r.users, totalUsers: r.paging && r.paging.total }));
+ },
+
+ requestGroups() {
+ const url = `${window.baseUrl}/api/permissions/groups`;
+ const data = { permission: this.props.permission.key, ps: MAX_ITEMS };
+ $.get(url, data).done(r => this.setState({ groups: r.groups, totalGroups: r.paging && r.paging.total }));
+ },
+
+ render() {
+ return (
+ <li className="panel panel-vertical" data-id={this.props.permission.key}>
+ <h3>{this.props.permission.name}</h3>
+ <p className="spacer-top" dangerouslySetInnerHTML={{ __html: this.props.permission.description }}/>
+ <ul className="list-inline spacer-top">
+ <PermissionUsers permission={this.props.permission}
+ max={MAX_ITEMS}
+ items={this.state.users}
+ total={this.state.totalUsers || this.props.permission.usersCount}
+ refresh={this.requestUsers}/>
+ <PermissionGroups permission={this.props.permission}
+ max={MAX_ITEMS}
+ items={this.state.groups}
+ total={this.state.totalGroups || this.props.permission.groupsCount}
+ refresh={this.requestGroups}/>
+ </ul>
+ </li>
+ );
+ }
+});
--- /dev/null
+import React from 'react';
+import Permission from './permission';
+
+export default React.createClass({
+ propTypes:{
+ permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
+ },
+
+ renderPermissions() {
+ return this.props.permissions.map(permission => {
+ return <Permission key={permission.key} permission={permission}/>
+ });
+ },
+
+ render() {
+ return <ul id="global-permissions-list">{this.renderPermissions()}</ul>;
+ }
+});
--- /dev/null
+<div class="modal-head">
+ <h2>Update users</h2>
+</div>
+<div class="modal-body">
+ <div class="js-modal-messages"></div>
+ <div id="global-permissions-groups"></div>
+</div>
+<div class="modal-foot">
+ <a href="#" class="js-modal-close" id="global-permissions-groups-done">Done</a>
+</div>
--- /dev/null
+<div class="modal-head">
+ <h2>Update users</h2>
+</div>
+<div class="modal-body">
+ <div class="js-modal-messages"></div>
+ <div id="global-permissions-users"></div>
+</div>
+<div class="modal-foot">
+ <a href="#" class="js-modal-close" id="global-permissions-users-done">Done</a>
+</div>
--- /dev/null
+define([
+ 'components/common/modals',
+ 'components/common/select-list',
+ './templates'
+], function (Modal) {
+
+ return Modal.extend({
+ template: Templates['global-permissions-users'],
+
+ onRender: function () {
+ this._super();
+ new window.SelectList({
+ el: this.$('#global-permissions-users'),
+ width: '100%',
+ readOnly: false,
+ focusSearch: false,
+ format: function (item) {
+ return item.name + '<br><span class="note">' + item.login + '</span>';
+ },
+ queryParam: 'q',
+ searchUrl: baseUrl + '/api/permissions/users?ps=100&permission=' + this.options.permission,
+ selectUrl: baseUrl + '/api/permissions/add_user',
+ deselectUrl: baseUrl + '/api/permissions/remove_user',
+ extra: {
+ permission: this.options.permission
+ },
+ selectParameter: 'login',
+ selectParameterValue: 'login',
+ parse: function (r) {
+ this.more = false;
+ return r.users;
+ }
+ });
+ },
+
+ onDestroy: function () {
+ this.options.refresh();
+ this._super();
+ }
+ });
+
+});
--- /dev/null
+import React from 'react';
+
+export default React.createClass({
+ propTypes: {
+ email: React.PropTypes.string,
+ size: React.PropTypes.number.isRequired
+ },
+
+ render() {
+ const shouldShowAvatar = window.SS && window.SS.lf && window.SS.lf.enableGravatar;
+ if (!shouldShowAvatar) {
+ return null;
+ }
+ const emailHash = window.md5(this.props.email || '').trim();
+ const url = ('' + window.SS.lf.gravatarServerUrl)
+ .replace('{EMAIL_MD5}', emailHash)
+ .replace('{SIZE}', this.props.size * 2);
+ return <img className="rounded" src={url} width={this.props.size} height={this.props.size} alt={this.props.email}/>;
+ }
+});
-<% content_for :script do %>
- <script>require(['components/common/select-list']);</script>
+<% content_for :extra_script do %>
+ <script>
+ require(['apps/global-permissions/app'], function (App) {
+ App.start({ el: '#content' });
+ });
+ </script>
<% end %>
-
-<div class="page">
- <header class="page-header">
- <h1 class="page-title"><%= h message 'global_permissions.page' -%></h1>
- <p class="page-description"><%= message('global_permissions.page.description') -%> </p>
- </header>
-
- <table class="data width100" id="global-permissions">
- <thead>
- <tr>
- <th><%= h message('global_permissions.permission') -%></th>
- <th width="30%"><%= h message('global_permissions.users') -%></th>
- <th width="30%"><%= h message('global_permissions.groups') -%></th>
- </tr>
- </thead>
- <tbody>
- <%
- permission_keys = Internal.permissions.globalPermissions()
- key_to_name = permission_keys.inject({}) do |hash, key|
- hash[key] = message("global_permissions.#{key}")
- hash
- end
- %>
-
- <%
- # Note that sorting by names should be case insensitive but it's not the case. It's not a problem in this page.
- key_to_name.sort { |a, b| a[1]<=>b[1] }.each do |elem|
- permission_key = elem[0]
- permission_name = elem[1]
- %>
- <tr class="<%= cycle('even', 'odd', :name => 'global_permission') -%>">
- <td valign="top">
- <b><%= h permission_name -%></b><br/>
- <span class="small gray"><%= message("global_permissions.#{permission_key}.desc") -%></span>
- </td>
- <td valign="top" style="word-break:break-all;width:30%;">
- <span id="users-<%= permission_key.parameterize -%>"><%= users(permission_key).map(&:name).join(', ') -%></span>
- (<%= link_to_edit_roles_permission_form(message('select'), permission_key, nil, "select-users-#{permission_key}") -%>)<br/>
- </td>
- <td valign="top" style="word-break:break-all;width:30%;">
- <span id="groups-<%= permission_key.parameterize -%>"><%= groups(permission_key).map { |g| group_name(g) }.join(', ') %></span>
- (<%= link_to_edit_groups_permission_form(message('select'), permission_key, nil, "select-groups-#{permission_key}") -%>)<br/>
- </td>
- </tr>
- <% end %>
- </tbody>
- </table>
-</div>
--- /dev/null
+{
+ "globalPermissions": [
+ {
+ "key": "admin",
+ "name": "Administer System",
+ "description": "Ability to perform all administration functions for the instance: global configuration and personalization of default dashboards.",
+ "usersCount": 1,
+ "groupsCount": 5
+ },
+ {
+ "key": "profileadmin",
+ "name": "Administer Quality Profiles and Gates",
+ "description": "Ability to perform any action on the quality profiles and gates.",
+ "usersCount": 1,
+ "groupsCount": 0
+ },
+ {
+ "key": "shareDashboard",
+ "name": "Share Dashboards And Filters",
+ "description": "Ability to share dashboards, issue filters and measure filters.",
+ "usersCount": 0,
+ "groupsCount": 1
+ },
+ {
+ "key": "scan",
+ "name": "Execute Analysis",
+ "description": "Ability to execute analyses, and to get all settings required to perform the analysis, even the secured ones like the scm account password, the jira account password, and so on.",
+ "usersCount": 0,
+ "groupsCount": 1
+ },
+ {
+ "key": "dryRunScan",
+ "name": "Execute Preview Analysis",
+ "description": "Ability to execute preview analysis (results are not pushed to the server). This permission does not include the ability to access secured settings such as the scm account password, the jira account password, and so on.<br/>This permission is <em>required</em> to execute preview analysis in Eclipse or via the Issues Report plugin.",
+ "usersCount": 0,
+ "groupsCount": 1
+ },
+ {
+ "key": "provisioning",
+ "name": "Provision Projects",
+ "description": "Ability to initialize project structure before first analysis.",
+ "usersCount": 0,
+ "groupsCount": 1
+ }
+ ]
+}
--- /dev/null
+{
+ "groups": [
+ {
+ "id": "3",
+ "name": "1",
+ "description": "",
+ "selected": true
+ },
+ {
+ "id": "4",
+ "name": "2",
+ "description": "",
+ "selected": true
+ },
+ {
+ "id": "5",
+ "name": "3",
+ "description": "",
+ "selected": true
+ }
+ ],
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 3,
+ "total": 5
+ }
+}
--- /dev/null
+{
+ "users": [
+ {
+ "login": "admin",
+ "name": "Administrator",
+ "selected": true
+ }
+ ],
+ "paging": {
+ "pageIndex": 1,
+ "pageSize": 3,
+ "total": 1
+ }
+}
'test/medium/coding-rules.spec',
'test/medium/custom-measures.spec',
'test/medium/quality-profiles.spec',
- 'test/medium/source-viewer.spec'
+ 'test/medium/source-viewer.spec',
+ 'test/medium/global-permissions.spec'
],
tunnel: tunnel,
updateCenterActive: true
};
</script>
- <script>requirejs.config({ baseUrl: baseUrl + '../../build/js' });</script>
+ <script>
+ requirejs.config({
+ baseUrl: baseUrl + '../../build/js',
+ paths: {
+ 'react': 'libs/third-party/react-with-addons'
+ }
+ });
+ </script>
</head>
<body>
<div id="content"></div>
--- /dev/null
+define(function (require) {
+ var bdd = require('intern!bdd');
+ require('../helpers/test-page');
+
+ bdd.describe('Global Permissions', function () {
+ bdd.it('should show permissions', function () {
+ return this.remote
+ .open()
+ .mockFromFile('/api/permissions/search_global_permissions', 'permissions/global-permissions.json')
+ .mockFromFile('/api/permissions/users', 'permissions/users.json')
+ .mockFromFile('/api/permissions/groups', 'permissions/groups.json')
+ .startApp('global-permissions')
+ .checkElementExist('#global-permissions-header')
+ .checkElementExist('#global-permissions-list')
+ .checkElementCount('#global-permissions-list > li', 6)
+ .checkElementInclude('#global-permissions-list > li h3', 'Administer System')
+ .checkElementInclude('#global-permissions-list > li p', 'Ability to perform all administration')
+ .checkElementInclude('#global-permissions-list > li ul > li:first-child', 'Administrator')
+ .checkElementInclude('#global-permissions-list > li ul > li:last-child', '1')
+ });
+ });
+});