Commit 468d727a authored by Alfredo Sumaran's avatar Alfredo Sumaran

Add support to groups in protected branch dropdown

Unselect all other roles when selecting “No one”

Update tests and handle “No one” role option

Fix "Projected" to "Protected" misspelling
parent 441d0354
...@@ -6,6 +6,12 @@ ...@@ -6,6 +6,12 @@
PUSH: 'push_access_levels', PUSH: 'push_access_levels',
}; };
const LEVEL_TYPES = {
ROLE: 'role',
USER: 'user',
GROUP: 'group'
};
gl.ProtectedBranchCreate = class { gl.ProtectedBranchCreate = class {
constructor() { constructor() {
this.$wrap = this.$form = $('#new_protected_branch'); this.$wrap = this.$form = $('#new_protected_branch');
...@@ -72,14 +78,18 @@ ...@@ -72,14 +78,18 @@
for (let i = 0; i < selectedItems.length; i++) { for (let i = 0; i < selectedItems.length; i++) {
let current = selectedItems[i]; let current = selectedItems[i];
if (current.type === 'user') { if (current.type === LEVEL_TYPES.USER) {
levelAttributes.push({ levelAttributes.push({
user_id: selectedItems[i].user_id user_id: selectedItems[i].user_id
}); });
} else if (current.type === 'role') { } else if (current.type === LEVEL_TYPES.ROLE) {
levelAttributes.push({ levelAttributes.push({
access_level: selectedItems[i].access_level access_level: selectedItems[i].access_level
}); });
} else if (current.type === LEVEL_TYPES.GROUP) {
levelAttributes.push({
group_id: selectedItems[i].group_id
});
} }
} }
......
...@@ -6,6 +6,12 @@ ...@@ -6,6 +6,12 @@
PUSH: 'push_access_levels', PUSH: 'push_access_levels',
}; };
const LEVEL_TYPES = {
ROLE: 'role',
USER: 'user',
GROUP: 'group'
};
gl.ProtectedBranchEdit = class { gl.ProtectedBranchEdit = class {
constructor(options) { constructor(options) {
this.$wraps = {}; this.$wraps = {};
...@@ -21,6 +27,7 @@ ...@@ -21,6 +27,7 @@
} }
buildDropdowns() { buildDropdowns() {
// Allowed to merge dropdown // Allowed to merge dropdown
this['merge_access_levels_dropdown'] = new gl.ProtectedBranchAccessDropdown({ this['merge_access_levels_dropdown'] = new gl.ProtectedBranchAccessDropdown({
accessLevel: ACCESS_LEVELS.MERGE, accessLevel: ACCESS_LEVELS.MERGE,
...@@ -95,25 +102,33 @@ ...@@ -95,25 +102,33 @@
let currentItem = items[i]; let currentItem = items[i];
if (currentItem.user_id) { if (currentItem.user_id) {
// Solo haciendo esto solo para usuarios por ahora
// obtenemos la data más actual de los items seleccionados // Do this only for users for now
// get the current data for selected items
let selectedItems = this[dropdownName].getSelectedItems(); let selectedItems = this[dropdownName].getSelectedItems();
let currentSelectedItem = _.findWhere(selectedItems, { user_id: currentItem.user_id }); let currentSelectedItem = _.findWhere(selectedItems, { user_id: currentItem.user_id });
itemToAdd = { itemToAdd = {
id: currentItem.id, id: currentItem.id,
user_id: currentItem.user_id, user_id: currentItem.user_id,
type: 'user', type: LEVEL_TYPES.USER,
persisted: true, persisted: true,
name: currentSelectedItem.name, name: currentSelectedItem.name,
username: currentSelectedItem.username, username: currentSelectedItem.username,
avatar_url: currentSelectedItem.avatar_url avatar_url: currentSelectedItem.avatar_url
} }
} else if (currentItem.group_id) {
itemToAdd = {
id: currentItem.id,
group_id: currentItem.group_id,
type: LEVEL_TYPES.GROUP,
persisted: true
};
} else { } else {
itemToAdd = { itemToAdd = {
id: currentItem.id, id: currentItem.id,
access_level: currentItem.access_level, access_level: currentItem.access_level,
type: 'role', type: LEVEL_TYPES.ROLE,
persisted: true persisted: true
} }
} }
......
// Modified version of `UsersSelect` for use with access selection for protected branches.
//
// - Selections are sent via AJAX if `saveOnSelect` is `true`
// - If `saveOnSelect` is `false`, the dropdown element must have a `field-name` data
// attribute. The DOM must contain two fields - "#{field-name}[access_level]" and "#{field_name}[user_id]"
// where the selections will be stored.
class ProtectedBranchesAccessSelect {
constructor(container, saveOnSelect, selectDefault) {
this.container = container;
this.saveOnSelect = saveOnSelect;
this.selectDefault = selectDefault;
this.usersPath = "/autocomplete/users.json";
this.setupDropdown(".allowed-to-merge", gon.merge_access_levels, gon.selected_merge_access_levels);
this.setupDropdown(".allowed-to-push", gon.push_access_levels, gon.selected_push_access_levels);
}
setupDropdown(className, accessLevels, selectedAccessLevels) {
this.container.find(className).each((i, element) => {
var dropdown = $(element).glDropdown({
clicked: _.chain(this.onSelect).partial(element).bind(this).value(),
data: (term, callback) => {
this.getUsers(term, (users) => {
users = _(users).map((user) => _(user).extend({ type: "user" }));
accessLevels = _(accessLevels).map((accessLevel) => _(accessLevel).extend({ type: "role" }));
var accessLevelsWithUsers = accessLevels.concat("divider", users);
callback(_(accessLevelsWithUsers).reject((item) => _.contains(selectedAccessLevels, item.id)));
});
},
filterable: true,
filterRemote: true,
search: { fields: ['name', 'username'] },
selectable: true,
toggleLabel: (selected) => $(element).data('default-label'),
renderRow: (user) => {
if (user.before_divider != null) {
return "<li> <a href='#'>" + user.text + " </a> </li>";
}
var username = user.username ? "@" + user.username : null;
var avatar = user.avatar_url ? user.avatar_url : false;
var img = avatar ? "<img src='" + avatar + "' class='avatar avatar-inline' width='30' />" : '';
var listWithName = "<li> <a href='#' class='dropdown-menu-user-link'> " + img + " <strong class='dropdown-menu-user-full-name'> " + user.name + " </strong>";
var listWithUserName = username ? "<span class='dropdown-menu-user-username'> " + username + " </span>" : '';
var listClosingTags = "</a> </li>";
return listWithName + listWithUserName + listClosingTags;
}
});
if (this.selectDefault) {
$(dropdown).find('.dropdown-toggle-text').text(accessLevels[0].text);
}
});
}
onSelect(dropdown, selected, element, e) {
$(dropdown).find('.dropdown-toggle-text').text(selected.text || selected.name);
var access_level = selected.type == 'user' ? 40 : selected.id;
var user_id = selected.type == 'user' ? selected.id : null;
if (this.saveOnSelect) {
$.ajax({
type: "POST",
url: $(dropdown).data('url'),
dataType: "json",
data: {
_method: 'PATCH',
id: $(dropdown).data('id'),
protected_branch: {
["" + ($(dropdown).data('type')) + "_attributes"]: [{
access_level: access_level,
user_id: user_id
}]
}
},
success: function() {
var row;
row = $(e.target);
row.closest('tr').effect('highlight');
row.closest('td').find('.access-levels-list').append("<li>" + selected.name + "</li>");
location.reload();
},
error: function() {
new Flash("Failed to update branch!", "alert");
}
});
} else {
var fieldName = $(dropdown).data('field-name');
$("input[name='" + fieldName + "[access_level]']").val(access_level);
$("input[name='" + fieldName + "[user_id]']").val(user_id);
}
}
getUsers(query, callback) {
var url = this.buildUrl(this.usersPath);
return $.ajax({
url: url,
data: {
search: query,
per_page: 20,
active: true,
project_id: gon.current_project_id,
push_code: true
},
dataType: "json"
}).done(function(users) {
callback(users);
});
}
buildUrl(url) {
if (gon.relative_url_root != null) {
url = gon.relative_url_root.replace(/\/$/, '') + url;
}
return url;
}
}
...@@ -41,6 +41,8 @@ module BranchesHelper ...@@ -41,6 +41,8 @@ module BranchesHelper
name: level.user.name, name: level.user.name,
avatar_url: level.user.avatar_url avatar_url: level.user.avatar_url
} }
elsif level.type == :group
{ id: level.id, type: level.type, group_id: level.group_id }
else else
{ id: level.id, type: level.type, access_level: level.access_level } { id: level.id, type: level.type, access_level: level.access_level }
end end
......
...@@ -2,6 +2,7 @@ RSpec.shared_examples "protected branches > access control > EE" do ...@@ -2,6 +2,7 @@ RSpec.shared_examples "protected branches > access control > EE" do
[['merge', ProtectedBranch::MergeAccessLevel], ['push', ProtectedBranch::PushAccessLevel]].each do |git_operation, access_level_class| [['merge', ProtectedBranch::MergeAccessLevel], ['push', ProtectedBranch::PushAccessLevel]].each do |git_operation, access_level_class|
# Need to set a default for the `git_operation` access level that _isn't_ being tested # Need to set a default for the `git_operation` access level that _isn't_ being tested
other_git_operation = git_operation == 'merge' ? 'push' : 'merge' other_git_operation = git_operation == 'merge' ? 'push' : 'merge'
roles = git_operation == 'merge' ? access_level_class.human_access_levels : access_level_class.human_access_levels.except(0)
let(:users) { create_list(:user, 5) } let(:users) { create_list(:user, 5) }
let(:groups) { create_list(:group, 5) } let(:groups) { create_list(:group, 5) }
...@@ -12,8 +13,6 @@ RSpec.shared_examples "protected branches > access control > EE" do ...@@ -12,8 +13,6 @@ RSpec.shared_examples "protected branches > access control > EE" do
end end
it "allows creating protected branches that roles, users, and groups can #{git_operation} to" do it "allows creating protected branches that roles, users, and groups can #{git_operation} to" do
roles = access_level_class.human_access_levels
visit namespace_project_protected_branches_path(project.namespace, project) visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('master') set_protected_branch_name('master')
...@@ -32,8 +31,6 @@ RSpec.shared_examples "protected branches > access control > EE" do ...@@ -32,8 +31,6 @@ RSpec.shared_examples "protected branches > access control > EE" do
end end
it "allows updating protected branches so that roles and users can #{git_operation} to it" do it "allows updating protected branches so that roles and users can #{git_operation} to it" do
roles = access_level_class.human_access_levels
visit namespace_project_protected_branches_path(project.namespace, project) visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('master') set_protected_branch_name('master')
set_allowed_to('merge') set_allowed_to('merge')
...@@ -42,7 +39,6 @@ RSpec.shared_examples "protected branches > access control > EE" do ...@@ -42,7 +39,6 @@ RSpec.shared_examples "protected branches > access control > EE" do
click_on "Protect" click_on "Protect"
within(".js-protected-branch-edit-form") do within(".js-protected-branch-edit-form") do
set_allowed_to(git_operation, users.map(&:name)) set_allowed_to(git_operation, users.map(&:name))
set_allowed_to(git_operation, groups.map(&:name)) set_allowed_to(git_operation, groups.map(&:name))
set_allowed_to(git_operation, roles.values) set_allowed_to(git_operation, roles.values)
...@@ -57,8 +53,6 @@ RSpec.shared_examples "protected branches > access control > EE" do ...@@ -57,8 +53,6 @@ RSpec.shared_examples "protected branches > access control > EE" do
end end
it "allows updating protected branches so that roles and users cannot #{git_operation} to it" do it "allows updating protected branches so that roles and users cannot #{git_operation} to it" do
roles = access_level_class.human_access_levels
visit namespace_project_protected_branches_path(project.namespace, project) visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('master') set_protected_branch_name('master')
...@@ -84,7 +78,6 @@ RSpec.shared_examples "protected branches > access control > EE" do ...@@ -84,7 +78,6 @@ RSpec.shared_examples "protected branches > access control > EE" do
it "prepends selected users that can #{git_operation} to" do it "prepends selected users that can #{git_operation} to" do
users = create_list(:user, 21) users = create_list(:user, 21)
users.each { |user| project.team << [user, :developer] } users.each { |user| project.team << [user, :developer] }
roles = access_level_class.human_access_levels
visit namespace_project_protected_branches_path(project.namespace, project) visit namespace_project_protected_branches_path(project.namespace, project)
...@@ -103,7 +96,6 @@ RSpec.shared_examples "protected branches > access control > EE" do ...@@ -103,7 +96,6 @@ RSpec.shared_examples "protected branches > access control > EE" do
click_on users.last.name click_on users.last.name
find(".js-allowed-to-#{git_operation}").click # close find(".js-allowed-to-#{git_operation}").click # close
end end
wait_for_ajax wait_for_ajax
# Verify the user is appended in the dropdown # Verify the user is appended in the dropdown
...@@ -115,4 +107,48 @@ RSpec.shared_examples "protected branches > access control > EE" do ...@@ -115,4 +107,48 @@ RSpec.shared_examples "protected branches > access control > EE" do
expect(ProtectedBranch.last.send("#{git_operation}_access_levels".to_sym).map(&:user_id)).to include(users.last.id) expect(ProtectedBranch.last.send("#{git_operation}_access_levels".to_sym).map(&:user_id)).to include(users.last.id)
end end
end end
context 'When updating a protected branch' do
it 'discards other roles when choosing "No one"' do
roles = ProtectedBranch::PushAccessLevel.human_access_levels.except(0)
visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('fix')
set_allowed_to('merge')
set_allowed_to('push', roles.values)
click_on "Protect"
wait_for_ajax
roles.each do |(access_type_id, _)|
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id)
end
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).not_to include(0)
within(".js-protected-branch-edit-form") do
set_allowed_to('push', 'No one')
end
wait_for_ajax
roles.each do |(access_type_id, _)|
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).not_to include(access_type_id)
end
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(0)
end
end
context 'When creating a protected branch' do
it 'discards other roles when choosing "No one"' do
roles = ProtectedBranch::PushAccessLevel.human_access_levels.except(0)
visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('master')
set_allowed_to('merge')
set_allowed_to('push', ProtectedBranch::PushAccessLevel.human_access_levels.values) # Last item (No one) should deselect the other ones
click_on "Protect"
wait_for_ajax
roles.each do |(access_type_id, _)|
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).not_to include(access_type_id)
end
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(0)
end
end
end end
require 'spec_helper' require 'spec_helper'
Dir["./spec/features/protected_branches/*.rb"].sort.each { |f| require f } Dir["./spec/features/protected_branches/*.rb"].sort.each { |f| require f }
feature 'Projected Branches', feature: true, js: true do feature 'Protected Branches', feature: true, js: true do
include WaitForAjax include WaitForAjax
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment