diff options
author | Stas Vilchik <vilchiks@gmail.com> | 2015-08-20 15:39:03 +0200 |
---|---|---|
committer | Stas Vilchik <vilchiks@gmail.com> | 2015-08-24 09:22:49 +0200 |
commit | 7a65a44016c32c5656e7aa5f0e174e670b6849b0 (patch) | |
tree | ea8840f4bc19efd99d78a917e797bc1c2902e98e /server/sonar-web | |
parent | c007b1899708a5cb4dce68087ed33a0c36551877 (diff) | |
download | sonarqube-7a65a44016c32c5656e7aa5f0e174e670b6849b0.tar.gz sonarqube-7a65a44016c32c5656e7aa5f0e174e670b6849b0.zip |
SONAR-6797 rewrite project permissions page
Diffstat (limited to 'server/sonar-web')
20 files changed, 465 insertions, 148 deletions
diff --git a/server/sonar-web/Gruntfile.coffee b/server/sonar-web/Gruntfile.coffee index 050967793ab..240adc7e642 100644 --- a/server/sonar-web/Gruntfile.coffee +++ b/server/sonar-web/Gruntfile.coffee @@ -146,6 +146,7 @@ module.exports = (grunt) -> 'build-app:metrics' 'build-app:nav' 'build-app:overview' + 'build-app:project-permissions' 'build-app:provisioning' 'build-app:quality-gates' 'build-app:quality-profiles' @@ -241,6 +242,9 @@ module.exports = (grunt) -> '<%= BUILD_PATH %>/js/apps/global-permissions/templates.js': [ '<%= SOURCE_PATH %>/js/apps/global-permissions/templates/**/*.hbs' ] + '<%= BUILD_PATH %>/js/apps/project-permissions/templates.js': [ + '<%= SOURCE_PATH %>/js/apps/project-permissions/templates/**/*.hbs' + ] clean: diff --git a/server/sonar-web/src/main/js/apps/nav/settings/settings-nav.jsx b/server/sonar-web/src/main/js/apps/nav/settings/settings-nav.jsx index 20f1ce5a08d..760034e7384 100644 --- a/server/sonar-web/src/main/js/apps/nav/settings/settings-nav.jsx +++ b/server/sonar-web/src/main/js/apps/nav/settings/settings-nav.jsx @@ -47,6 +47,7 @@ export default React.createClass({ {this.renderLink('/groups', window.t('user_groups.page'))} {this.renderLink('/roles/global', window.t('global_permissions.page'))} {this.renderLink('/roles/projects', window.t('roles.page'))} + {this.renderLink('/permission_templates', window.t('permission_templates'))} </ul> </li> diff --git a/server/sonar-web/src/main/js/apps/project-permissions/app.jsx b/server/sonar-web/src/main/js/apps/project-permissions/app.jsx new file mode 100644 index 00000000000..28dc73b7f42 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/app.jsx @@ -0,0 +1,13 @@ +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); + }); + } +}; diff --git a/server/sonar-web/src/main/js/apps/project-permissions/groups-view.js b/server/sonar-web/src/main/js/apps/project-permissions/groups-view.js new file mode 100644 index 00000000000..ba98ca7e525 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/groups-view.js @@ -0,0 +1,43 @@ +define([ + 'components/common/modals', + 'components/common/select-list', + './templates' +], function (Modal) { + + return Modal.extend({ + template: Templates['project-permissions-groups'], + + onRender: function () { + this._super(); + new window.SelectList({ + el: this.$('#project-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 + '&projectId=' + this.options.project, + selectUrl: baseUrl + '/api/permissions/add_group', + deselectUrl: baseUrl + '/api/permissions/remove_group', + extra: { + permission: this.options.permission, + projectId: this.options.project + }, + selectParameter: 'groupName', + selectParameterValue: 'name', + parse: function (r) { + this.more = false; + return r.groups; + } + }); + }, + + onDestroy: function () { + this.options.refresh && this.options.refresh(); + this._super(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/apps/project-permissions/main.jsx b/server/sonar-web/src/main/js/apps/project-permissions/main.jsx new file mode 100644 index 00000000000..96b77f7cb72 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/main.jsx @@ -0,0 +1,80 @@ +import React from 'react'; +import Permissions from './permissions'; +import PermissionsFooter from './permissions-footer'; +import Search from './search'; + +let $ = jQuery; + +export default React.createClass({ + getInitialState() { + return { permissions: [], projects: [], total: 0 }; + }, + + componentDidMount() { + this.requestPermissions(); + }, + + mergePermissionsToProjects(projects, basePermissions) { + return projects.map(project => { + // it's important to keep the order of the project permissions the same as the order of base permissions + let permissions = basePermissions.map(basePermission => { + let projectPermission = _.findWhere(project.permissions, { key: basePermission.key }); + if (!projectPermission) { + throw new Error(`Project "${project.name} [${project.key}]" doesn't have permission "${basePermission.key}"`); + } + return _.extend({}, basePermission, projectPermission); + }); + return _.extend({}, project, { permissions: permissions }); + }); + }, + + requestPermissions(page = 1, query = '') { + let url = `${window.baseUrl}/api/permissions/search_project_permissions`; + let data = { p: page, q: query }; + $.get(url, data).done(r => { + let projects = this.mergePermissionsToProjects(r.projects, r.permissions); + if (page > 1) { + projects = [].concat(this.state.projects, projects); + } + this.setState({ + projects: projects, + permissions: r.permissions, + total: r.paging.total, + page: r.paging.pageIndex, + query: query + }); + }); + }, + + loadMore() { + this.requestPermissions(this.state.page + 1, this.state.query); + }, + + search(query) { + this.requestPermissions(1, query); + }, + + render() { + return ( + <div className="page"> + <header id="project-permissions-header" className="page-header"> + <h1 className="page-title">{window.t('roles.page')}</h1> + <p className="page-description">{window.t('roles.page.description2')}</p> + </header> + + <Search + search={this.search}/> + + <Permissions + projects={this.state.projects} + permissions={this.state.permissions} + refresh={this.requestPermissions}/> + + <PermissionsFooter + count={this.state.projects.length} + total={this.state.total} + loadMore={this.loadMore}/> + </div> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions-footer.jsx b/server/sonar-web/src/main/js/apps/project-permissions/permissions-footer.jsx new file mode 100644 index 00000000000..f4913a753c2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions-footer.jsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export default React.createClass({ + propTypes:{ + count: React.PropTypes.number.isRequired, + total: React.PropTypes.number.isRequired, + loadMore: React.PropTypes.func.isRequired + }, + + render() { + let hasMore = this.props.total > this.props.count; + let loadMoreLink = <a onClick={this.props.loadMore} className="spacer-left" href="#">show more</a>; + return ( + <footer className="spacer-top note text-center"> + {this.props.count}/{this.props.total} shown + {hasMore ? loadMoreLink : null} + </footer> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx b/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx new file mode 100644 index 00000000000..a68cca21fc2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions-header.jsx @@ -0,0 +1,26 @@ +import React from 'react'; + +export default React.createClass({ + propTypes: { + permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired + }, + + render() { + let cellWidth = (80 / this.props.permissions.length) + '%'; + let cells = this.props.permissions.map(p => { + return ( + <th key={p.key} style={{ width: cellWidth }}> + {p.name}<br/><span className="small">{p.description}</span> + </th> + ); + }); + return ( + <thead> + <tr> + <th style={{ width: '20%' }}> </th> + {cells} + </tr> + </thead> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx b/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx new file mode 100644 index 00000000000..4638c209651 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/permissions.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import PermissionsHeader from './permissions-header'; +import Project from './project'; + +export default React.createClass({ + propTypes:{ + projects: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired, + refresh: React.PropTypes.func.isRequired + }, + + render() { + let projects = this.props.projects.map(p => { + return <Project key={p.uuid} project={p} refresh={this.props.refresh}/> + }); + return ( + <table id="projects" className="data zebra"> + <PermissionsHeader permissions={this.props.permissions}/> + <tbody>{projects}</tbody> + </table> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/project-permissions/project.jsx b/server/sonar-web/src/main/js/apps/project-permissions/project.jsx new file mode 100644 index 00000000000..31c03a1572f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/project.jsx @@ -0,0 +1,66 @@ +import React from 'react'; +import UsersView from './users-view'; +import GroupsView from './groups-view'; +import {getProjectUrl} from '../../helpers/Url'; + +export default React.createClass({ + propTypes: { + project: React.PropTypes.object.isRequired, + refresh: React.PropTypes.func.isRequired + }, + + showGroups(permission, e) { + e.preventDefault(); + new GroupsView({ + permission: permission, + project: this.props.project.uuid, + refresh: this.props.refresh + }).render(); + }, + + showUsers(permission, e) { + e.preventDefault(); + new UsersView({ + permission: permission, + project: this.props.project.uuid, + refresh: this.props.refresh + }).render(); + }, + + render() { + let permissions = this.props.project.permissions.map(p => { + return ( + <td key={p.key}> + <table> + <tr> + <td className="spacer-right">Users</td> + <td className="spacer-left bordered-left">{p.usersCount}</td> + <td className="spacer-left"> + <a onClick={this.showUsers.bind(this, p.key)} className="icon-bullet-list" title="Update Users" + data-toggle="tooltip" href="#"></a> + </td> + </tr> + <tr> + <td className="spacer-right">Groups</td> + <td className="spacer-left bordered-left">{p.groupsCount}</td> + <td className="spacer-left"> + <a onClick={this.showGroups.bind(this, p.key)} className="icon-bullet-list" title="Update Users" + data-toggle="tooltip" href="#"></a> + </td> + </tr> + </table> + </td> + ); + }); + return ( + <tr> + <td> + <strong> + <a href={getProjectUrl(this.props.project.key)}>{this.props.project.name}</a> + </strong> + </td> + {permissions} + </tr> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/project-permissions/search.jsx b/server/sonar-web/src/main/js/apps/project-permissions/search.jsx new file mode 100644 index 00000000000..3506d78834b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/search.jsx @@ -0,0 +1,34 @@ +import React from 'react'; + +export default React.createClass({ + propTypes: { + search: React.PropTypes.func.isRequired + }, + + componentWillMount: function () { + this.search = _.debounce(this.search, 250); + }, + + onSubmit(e) { + e.preventDefault(); + this.search(); + }, + + search() { + let q = React.findDOMNode(this.refs.input).value; + this.props.search(q); + }, + + render() { + return ( + <div className="panel panel-vertical bordered-bottom spacer-bottom"> + <form onSubmit={this.onSubmit} className="search-box"> + <button className="search-box-submit button-clean"> + <i className="icon-search"></i> + </button> + <input onChange={this.search} ref="input" className="search-box-input" type="search" placeholder="Search"/> + </form> + </div> + ); + } +}); diff --git a/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-groups.hbs b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-groups.hbs new file mode 100644 index 00000000000..c5f551e3682 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-groups.hbs @@ -0,0 +1,10 @@ +<div class="modal-head"> + <h2>Update Groups</h2> +</div> +<div class="modal-body"> + <div class="js-modal-messages"></div> + <div id="project-permissions-groups"></div> +</div> +<div class="modal-foot"> + <a href="#" class="js-modal-close">Done</a> +</div> diff --git a/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-users.hbs b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-users.hbs new file mode 100644 index 00000000000..acfd4eaf75d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/templates/project-permissions-users.hbs @@ -0,0 +1,10 @@ +<div class="modal-head"> + <h2>Update Users</h2> +</div> +<div class="modal-body"> + <div class="js-modal-messages"></div> + <div id="project-permissions-users"></div> +</div> +<div class="modal-foot"> + <a href="#" class="js-modal-close">Done</a> +</div> diff --git a/server/sonar-web/src/main/js/apps/project-permissions/users-view.js b/server/sonar-web/src/main/js/apps/project-permissions/users-view.js new file mode 100644 index 00000000000..6a715835ba7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/project-permissions/users-view.js @@ -0,0 +1,43 @@ +define([ + 'components/common/modals', + 'components/common/select-list', + './templates' +], function (Modal) { + + return Modal.extend({ + template: Templates['project-permissions-users'], + + onRender: function () { + this._super(); + new window.SelectList({ + el: this.$('#project-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 + '&projectId=' + this.options.project, + selectUrl: baseUrl + '/api/permissions/add_user', + deselectUrl: baseUrl + '/api/permissions/remove_user', + extra: { + permission: this.options.permission, + projectId: this.options.project + }, + selectParameter: 'login', + selectParameterValue: 'login', + parse: function (r) { + this.more = false; + return r.users; + } + }); + }, + + onDestroy: function () { + this.options.refresh && this.options.refresh(); + this._super(); + } + }); + +}); diff --git a/server/sonar-web/src/main/js/helpers/Url.jsx b/server/sonar-web/src/main/js/helpers/Url.jsx new file mode 100644 index 00000000000..e88ddb55808 --- /dev/null +++ b/server/sonar-web/src/main/js/helpers/Url.jsx @@ -0,0 +1,6 @@ +export function getProjectUrl(project) { + if (typeof project !== 'string') { + throw new TypeError("Project ID or KEY should be passed"); + } + return `${window.baseUrl}/overview?id=${encodeURIComponent(project)}`; +} diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/roles_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/roles_controller.rb index 01abb33a423..5cdcf358245 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/roles_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/roles_controller.rb @@ -33,23 +33,6 @@ class RolesController < ApplicationController # GET /roles/projects def projects access_denied unless has_role?(:admin) - - params['pageSize'] = 25 - params['qualifiers'] ||= 'TRK' - @query_result = Internal.component_api.findWithUncompleteProjects(params) - - @available_qualifiers = java_facade.getQualifiersWithProperty('hasRolePolicy').collect { |qualifier| [message("qualifiers.#{qualifier}"), qualifier] }.to_a.sort - - # For the moment, we return projects from rails models, but it should be replaced to return java components (this will need methods on ComponentQueryResult to return roles from component) - @projects = Project.all( - :include => ['user_roles','group_roles'], - :conditions => ['kee in (?)', @query_result.components().to_a.collect{|component| component.key()}], - # Even if components are already sorted, we must sort them again as this SQL query will not keep order - :order => 'lower(name)' - ) - @components_names = params[:names] - @components_keys = params[:keys] - @components_qualifiers = params[:qualifiers] end # GET /roles/edit_users[?resource=<resource>] diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb index 8f7b4840298..965847fcb40 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/permission_templates/index.html.erb @@ -5,12 +5,6 @@ <div class="page"> <header class="page-header"> <h1 class="page-title"><%= message 'roles.page' -%></h1> - <p class="page-description"><%= message('roles.page.description') -%></p> - </header> - - <%= render :partial => 'roles/tabs', :locals => {:selected_tab => 'Permission templates'} %> - <br/> - <header class="page-header"> <div class="page-actions"> <div class="button-group"> <%= link_to message('permission_template.set_default_templates'), {:action => :default_templates_form, :qualifiers => @root_qualifiers}, @@ -18,6 +12,7 @@ <%= link_to message('create'), {:action => :create_form}, :id => 'create-link-permission-template', :class => 'open-modal link-action button' %> </div> </div> + <p class="page-description"><%= message('roles.page.description') -%></p> </header> <table class="data width100" id="permission-templates"> <thead> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb b/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb index b383932b5a5..db45ae6982e 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/views/roles/projects.html.erb @@ -1,125 +1,7 @@ -<% content_for :script do %> - <script>require(['components/common/select-list']);</script> +<% content_for :extra_script do %> + <script> + require(['apps/project-permissions/app'], function (App) { + App.start({ el: '#content' }); + }); + </script> <% end %> - -<div class="page"> - <header class="page-header"> - <h1 class="page-title"><%= message 'roles.page' -%></h1> - <p class="page-description"><%= message('roles.page.description2') -%></p> - </header> - - <%= render :partial => 'roles/tabs', :locals => {:selected_tab => 'Projects'} %> - - <div class="tabs-panel marginbottom10 background-gray"> - <% form_tag({:action => 'projects'}, {:id => 'project-search-form', :method => 'get'}) do %> - <div class="table"> - <div class="project-search text-top"> - <span class="note"><%= message('projects_role.criteria.name') -%></span><br/> - <% selected_name = @query_result.query.names.to_a.first if @query_result.query.names && @query_result.query.names.size == 1 %> - <%= text_field_tag 'names', selected_name, :id => 'search-text' %> - </div> - <div class="project-search text-top"> - <span class="note"><%= message('projects_role.criteria.key') -%></span><br/> - <% selected_key = @query_result.query.keys.to_a.first if @query_result.query.keys && @query_result.query.keys.size == 1 %> - <%= text_field_tag 'keys', selected_key, :id => 'search-key' %> - </div> - <div class="project-search text-top"> - <span class="note"><%= message('type') -%></span><br/> - <% selected_qualifier = @query_result.query.qualifiers.to_a.first if @query_result.query.qualifiers && @query_result.query.qualifiers.size == 1 %> - <%= dropdown_tag 'qualifiers', options_for_select(@available_qualifiers, selected_qualifier), { - :width => '150px' - }, {:id => 'search-qualifier'} -%> - </div> - <div class="project-search"> - <br/> - <%= submit_tag message('search_verb'), :id => 'submit-search', :onclick => 'submitSearch();' %> - </div> - </div> - <% end %> - </div> - - <header class="page-header"> - <div class="page-actions" id="project-roles-operations"> - <div class="button-group"> - <%= link_to message('projects_role.bulk_change'), {:action => :apply_template_form, :names => @components_names, - :keys => @components_keys, :qualifiers => @components_qualifiers, - :results_count => @query_result.paging.total}, - :id => 'apply-template-modal', :class => 'open-modal link-action button' %> - </div> - </div> - </header> - - <table class="data width100" id="projects"> - <thead> - <tr> - <th style="min-width: 10em"> </th> - <th> - <%= message('projects_role.user') -%><br/> - <span class="small gray" style="font-size: 11px; font-weight: normal;"><%= message('projects_role.user.desc') -%></span> - </th> - <th> - <%= message('projects_role.admin') -%><br/> - <span class="small gray" style="font-size: 11px; font-weight: normal;"><%= message('projects_role.admin.desc') -%></span> - </th> - <th> - <%= message('projects_role.issueadmin') -%><br/> - <span class="small gray" style="font-size: 11px; font-weight: normal;"><%= message('projects_role.issueadmin.desc') -%></span> - </th> - <th> - <%= message('projects_role.codeviewer') -%><br/> - <span class="small gray" style="font-size: 11px; font-weight: normal;"><%= message('projects_role.codeviewer.desc') -%></span> - </th> - <th> </th> - </tr> - </thead> - - <%= paginate_java(@query_result.paging, :colspan => 4, :id => 'project-roles-foot', :include_loading_icon => true) { |label, page_id| - link_to(label, params.merge({:pageIndex => page_id})) - } - %> - - <tbody> - <% if @projects.empty? %> - <tr class="even"> - <td colspan="5" align="left"><%= message('no_results') %></td> - </tr> - <% end - - @projects.each do |project| - %> - <tr class="<%= cycle('even', 'odd') -%>"> - <td valign="top"><b><%= h project.name %></b><br/> - <span class="small gray"><%= h project.key -%></span> - </td> - <% ['user', 'admin', 'issueadmin', 'codeviewer'].each do |permission| -%> - <td valign="top"> - <% - users=Api::Utils.insensitive_sort(project.user_roles.select { |ur| ur.role==permission }.map { |ur| ur.user.name }) - groups=Api::Utils.insensitive_sort(project.group_roles.select { |gr| gr.role==permission }.map { |gr| group_name(gr.group) }) - %> - <span id="u-<%= permission -%>-<%= u project.kee -%>"><%= users.join(', ') %></span> - (<%= link_to_edit_roles_permission_form(message('select users'), permission, project.id, "select-u-#{permission}-#{u project.kee}") %>)<br/> - <span id="g-<%= permission -%>-<%= u project.kee -%>"><%= groups.join(', ') %></span> - (<%= link_to_edit_groups_permission_form(message('select groups'), permission, project.id, "select-g-#{permission}-#{u project.kee}") %>)<br/> - </td> - <% end %> - <td align="right"> - <%= link_to message('projects_role.apply_template'), {:action => :apply_template_form, :components => [project.key], :names => project.name, - :results_count => 1, :qualifiers => @components_qualifiers}, - :id => "apply-template-#{u project.kee}", :class => 'open-modal link-action' %> - </td> - </tr> - <% - end %> - </tbody> - </table> -</div> - - -<script> - function submitSearch () { - $j("#project-search-form").submit(); - } - - $j('#search-text').focus(); -</script> diff --git a/server/sonar-web/src/test/json/permissions/project-permissions.json b/server/sonar-web/src/test/json/permissions/project-permissions.json new file mode 100644 index 00000000000..657f05042ef --- /dev/null +++ b/server/sonar-web/src/test/json/permissions/project-permissions.json @@ -0,0 +1,55 @@ +{ + "projects": [ + { + "uuid": "10c394cc-c37c-4cf0-97b9-360165c47270", + "key": "my-project", + "name": "My Project", + "permissions": [ + { + "key": "admin", + "usersCount": 1, + "groupsCount": 2 + }, + { + "key": "codeviewer", + "usersCount": 3, + "groupsCount": 4 + } + ] + }, + { + "uuid": "5d2408c9-b5c6-4426-8ee8-05be930a5f62", + "key": "another-project", + "name": "Another Project", + "permissions": [ + { + "key": "admin", + "usersCount": 5, + "groupsCount": 6 + }, + { + "key": "codeviewer", + "usersCount": 7, + "groupsCount": 8 + } + ] + } + ], + "permissions": [ + { + "key": "admin", + "name": "Administer", + "description": "Ability to access project settings and perform administration tasks. (Users will also need \"Browse\" permission)" + }, + { + "key": "codeviewer", + "name": "See Source Code", + "description": "Ability to view the project's source code. (Users will also need \"Browse\" permission)" + } + ], + "paging": { + "pageIndex": 1, + "pageSize": 25, + "total": 2 + } +} diff --git a/server/sonar-web/test/intern.js b/server/sonar-web/test/intern.js index ee712c33f18..520d2127a40 100644 --- a/server/sonar-web/test/intern.js +++ b/server/sonar-web/test/intern.js @@ -31,7 +31,8 @@ define(['intern'], function (intern) { 'test/medium/custom-measures.spec', 'test/medium/quality-profiles.spec', 'test/medium/source-viewer.spec', - 'test/medium/global-permissions.spec' + 'test/medium/global-permissions.spec', + 'test/medium/project-permissions.spec' ], tunnel: tunnel, diff --git a/server/sonar-web/test/medium/project-permissions.spec.js b/server/sonar-web/test/medium/project-permissions.spec.js new file mode 100644 index 00000000000..1e32733fbf3 --- /dev/null +++ b/server/sonar-web/test/medium/project-permissions.spec.js @@ -0,0 +1,22 @@ +define(function (require) { + var bdd = require('intern!bdd'); + require('../helpers/test-page'); + + bdd.describe('Project Permissions', function () { + bdd.it('should show permissions', function () { + return this.remote + .open() + .mockFromFile('/api/permissions/search_project_permissions', 'permissions/project-permissions.json') + .startApp('project-permissions') + .checkElementExist('#project-permissions-header') + .checkElementExist('#projects') + .checkElementCount('#projects > thead > tr > th', 3) + .checkElementCount('#projects > tbody > tr', 2) + .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(1)', 'My Project') + .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '1') + .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(2)', '2') + .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '3') + .checkElementInclude('#projects > tbody > tr:first-child td:nth-child(3)', '4'); + }); + }); +}); |