Merge branch 'branch-permissions' into 'master'

Branch permissions layout

CE part of https://gitlab.com/gitlab-org/gitlab-ee/issues/179

Related javascript code has been refactored and also layout has been updated.

![Screen_Shot_2016-08-04_at_3.23.19_PM](/uploads/a0f6165e255b3f93dcb80eaa3f3318e4/Screen_Shot_2016-08-04_at_3.23.19_PM.png)


See merge request !5652
This commit is contained in:
Jacob Schatz 2016-08-08 13:38:04 +00:00
commit b767d86885
18 changed files with 350 additions and 229 deletions

View file

@ -173,8 +173,8 @@
new Search();
break;
case 'projects:protected_branches:index':
new ProtectedBranchesAccessSelect($(".new_protected_branch"), false, true);
new ProtectedBranchesAccessSelect($(".protected-branches-list"), true, false);
new gl.ProtectedBranchCreate();
new gl.ProtectedBranchEditList();
break;
}
switch (path.first()) {

View file

@ -607,7 +607,7 @@
return this.dropdown.before($input);
};
GitLabDropdown.prototype.selectRowAtIndex = function(e, index) {
GitLabDropdown.prototype.selectRowAtIndex = function(index) {
var $el, selector;
selector = ".dropdown-content li:not(.divider,.dropdown-header,.separator):eq(" + index + ") a";
if (this.dropdown.find(".dropdown-toggle-page").length) {
@ -615,8 +615,6 @@
}
$el = $(selector, this.dropdown);
if ($el.length) {
e.preventDefault();
e.stopImmediatePropagation();
return $el.first().trigger('click');
}
};
@ -653,7 +651,7 @@
return false;
}
if (currentKeyCode === 13 && currentIndex !== -1) {
return _this.selectRowAtIndex(e, $('.is-focused', _this.dropdown).closest('li').index() - 1);
return _this.selectRowAtIndex($('.is-focused', _this.dropdown).closest('li').index() - 1);
}
};
})(this));

View file

@ -0,0 +1,24 @@
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchAccessDropdown = class {
constructor(options) {
const { $dropdown, data, onSelect } = options;
$dropdown.glDropdown({
data: data,
selectable: true,
inputId: $dropdown.data('input-id'),
fieldName: $dropdown.data('field-name'),
toggleLabel(item) {
return item.text;
},
clicked(item, $el, e) {
e.preventDefault();
onSelect();
}
});
}
}
})(window);

View file

@ -0,0 +1,56 @@
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchCreate = class {
constructor() {
this.$wrap = this.$form = $('#new_protected_branch');
this.buildDropdowns();
}
buildDropdowns() {
const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
// Cache callback
this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Merge dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: $allowedToMergeDropdown,
data: gon.merge_access_levels,
onSelect: this.onSelectCallback
});
// Allowed to Push dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: $allowedToPushDropdown,
data: gon.push_access_levels,
onSelect: this.onSelectCallback
});
// Select default
$allowedToPushDropdown.data('glDropdown').selectRowAtIndex(0);
$allowedToMergeDropdown.data('glDropdown').selectRowAtIndex(0);
// Protected branch dropdown
new ProtectedBranchDropdown({
$dropdown: this.$wrap.find('.js-protected-branch-select'),
onSelect: this.onSelectCallback
});
}
// This will run after clicked callback
onSelect() {
// Enable submit button
const $branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$wrap.find('input[name="protected_branch[merge_access_level_attributes][access_level]"]');
const $allowedToPushInput = this.$wrap.find('input[name="protected_branch[push_access_level_attributes][access_level]"]');
if ($branchInput.val() && $allowedToMergeInput.val() && $allowedToPushInput.val()){
this.$form.find('input[type="submit"]').removeAttr('disabled');
}
}
}
})(window);

View file

@ -0,0 +1,75 @@
class ProtectedBranchDropdown {
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
this.$dropdownContainer = this.$dropdown.parent();
this.$dropdownFooter = this.$dropdownContainer.find('.dropdown-footer');
this.$protectedBranch = this.$dropdownContainer.find('.create-new-protected-branch');
this.buildDropdown();
this.bindEvents();
// Hide footer
this.$dropdownFooter.addClass('hidden');
}
buildDropdown() {
this.$dropdown.glDropdown({
data: this.getProtectedBranches.bind(this),
filterable: true,
remote: false,
search: {
fields: ['title']
},
selectable: true,
toggleLabel(selected) {
return (selected && 'id' in selected) ? selected.title : 'Protected Branch';
},
fieldName: 'protected_branch[name]',
text(protectedBranch) {
return _.escape(protectedBranch.title);
},
id(protectedBranch) {
return _.escape(protectedBranch.id);
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (item, $el, e) => {
e.preventDefault();
this.onSelect();
}
});
}
bindEvents() {
this.$protectedBranch.on('click', this.onClickCreateWildcard.bind(this));
}
onClickCreateWildcard() {
this.$dropdown.data('glDropdown').remote.execute();
this.$dropdown.data('glDropdown').selectRowAtIndex(0);
}
getProtectedBranches(term, callback) {
if (this.selectedBranch) {
callback(gon.open_branches.concat(this.selectedBranch));
} else {
callback(gon.open_branches);
}
}
toggleCreateNewButton(branchName) {
this.selectedBranch = {
title: branchName,
id: branchName,
text: branchName
};
if (branchName) {
this.$dropdownContainer
.find('.create-new-protected-branch code')
.text(branchName);
}
this.$dropdownFooter.toggleClass('hidden', !branchName);
}
}

View file

@ -0,0 +1,61 @@
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchEdit = class {
constructor(options) {
this.$wrap = options.$wrap;
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
this.buildDropdowns();
}
buildDropdowns() {
// Allowed to merge dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToMergeDropdown,
data: gon.merge_access_levels,
onSelect: this.onSelect.bind(this)
});
// Allowed to push dropdown
new gl.ProtectedBranchAccessDropdown({
$dropdown: this.$allowedToPushDropdown,
data: gon.push_access_levels,
onSelect: this.onSelect.bind(this)
});
}
onSelect() {
const $allowedToMergeInput = this.$wrap.find(`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`);
const $allowedToPushInput = this.$wrap.find(`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`);
$.ajax({
type: 'POST',
url: this.$wrap.data('url'),
dataType: 'json',
data: {
_method: 'PATCH',
id: this.$wrap.data('banchId'),
protected_branch: {
merge_access_level_attributes: {
access_level: $allowedToMergeInput.val()
},
push_access_level_attributes: {
access_level: $allowedToPushInput.val()
}
}
},
success: () => {
this.$wrap.effect('highlight');
},
error() {
$.scrollTo(0);
new Flash('Failed to update branch!');
}
});
}
}
})(window);

View file

@ -0,0 +1,17 @@
(global => {
global.gl = global.gl || {};
gl.ProtectedBranchEditList = class {
constructor() {
this.$wrap = $('.protected-branches-list');
// Build edit forms
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new gl.ProtectedBranchEdit({
$wrap: $(el)
});
});
}
}
})(window);

View file

@ -1,72 +0,0 @@
(function() {
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.ProtectedBranchSelect = (function() {
function ProtectedBranchSelect(currentProject) {
this.toggleCreateNewButton = bind(this.toggleCreateNewButton, this);
this.getProtectedBranches = bind(this.getProtectedBranches, this);
$('.dropdown-footer').hide();
this.dropdown = $('.js-protected-branch-select').glDropdown({
data: this.getProtectedBranches,
filterable: true,
remote: false,
search: {
fields: ['title']
},
selectable: true,
toggleLabel: function(selected) {
if (selected && 'id' in selected) {
return selected.title;
} else {
return 'Protected Branch';
}
},
fieldName: 'protected_branch[name]',
text: function(protected_branch) {
return _.escape(protected_branch.title);
},
id: function(protected_branch) {
return _.escape(protected_branch.id);
},
onFilter: this.toggleCreateNewButton,
clicked: function() {
return $('.protect-branch-btn').attr('disabled', false);
}
});
$('.create-new-protected-branch').on('click', (function(_this) {
return function(event) {
_this.dropdown.data('glDropdown').remote.execute();
return _this.dropdown.data('glDropdown').selectRowAtIndex(event, 0);
};
})(this));
}
ProtectedBranchSelect.prototype.getProtectedBranches = function(term, callback) {
if (this.selectedBranch) {
return callback(gon.open_branches.concat(this.selectedBranch));
} else {
return callback(gon.open_branches);
}
};
ProtectedBranchSelect.prototype.toggleCreateNewButton = function(branchName) {
this.selectedBranch = {
title: branchName,
id: branchName,
text: branchName
};
if (branchName === '') {
$('.protected-branch-select-footer-list').addClass('hidden');
return $('.dropdown-footer').hide();
} else {
$('.create-new-protected-branch').text("Create Protected Branch: " + branchName);
$('.protected-branch-select-footer-list').removeClass('hidden');
return $('.dropdown-footer').show();
}
};
return ProtectedBranchSelect;
})();
}).call(this);

View file

@ -1,63 +0,0 @@
class ProtectedBranchesAccessSelect {
constructor(container, saveOnSelect, selectDefault) {
this.container = container;
this.saveOnSelect = saveOnSelect;
this.container.find(".allowed-to-merge").each((i, element) => {
var fieldName = $(element).data('field-name');
var dropdown = $(element).glDropdown({
data: gon.merge_access_levels,
selectable: true,
fieldName: fieldName,
clicked: _.chain(this.onSelect).partial(element).bind(this).value()
});
if (selectDefault) {
dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0);
}
});
this.container.find(".allowed-to-push").each((i, element) => {
var fieldName = $(element).data('field-name');
var dropdown = $(element).glDropdown({
data: gon.push_access_levels,
selectable: true,
fieldName: fieldName,
clicked: _.chain(this.onSelect).partial(element).bind(this).value()
});
if (selectDefault) {
dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0);
}
});
}
onSelect(dropdown, selected, element, e) {
$(dropdown).find('.dropdown-toggle-text').text(selected.text);
if (this.saveOnSelect) {
return $.ajax({
type: "POST",
url: $(dropdown).data('url'),
dataType: "json",
data: {
_method: 'PATCH',
id: $(dropdown).data('id'),
protected_branch: {
["" + ($(dropdown).data('type')) + "_attributes"]: {
"access_level": selected.id
}
}
},
success: function() {
var row;
row = $(e.target);
return row.closest('tr').effect('highlight');
},
error: function() {
return new Flash("Failed to update branch!", "alert");
}
});
}
}
}

View file

@ -72,6 +72,14 @@
&.large {
width: 200px;
}
&.wide {
width: 100%;
+ .dropdown-select {
width: 100%;
}
}
}
.dropdown-menu,

View file

@ -23,4 +23,9 @@
margin-top: $gl-padding;
}
}
.panel-title {
font-size: inherit;
line-height: inherit;
}
}

View file

@ -656,13 +656,9 @@ pre.light-well {
}
.new_protected_branch {
.dropdown {
display: inline;
margin-left: 15px;
}
label {
min-width: 120px;
margin-top: 6px;
font-weight: normal;
}
}
@ -678,6 +674,21 @@ pre.light-well {
font-weight: 600;
}
}
.settings-message {
margin: 0;
border-radius: 0 0 1px 1px;
padding: 20px 0;
border: none;
}
.table-bordered {
border-radius: 1px;
th:not(:last-child), td:not(:last-child) {
border-right: solid 1px transparent;
}
}
}
.custom-notifications-form {

View file

@ -1,26 +1,28 @@
%h5.prepend-top-0
Already Protected (#{@protected_branches.size})
- if @protected_branches.empty?
%p.settings-message.text-center
No branches are protected, protect a branch with the form above.
- else
- can_admin_project = can?(current_user, :admin_project, @project)
.panel.panel-default.protected-branches-list
- if @protected_branches.empty?
.panel-heading
%h3.panel-title
Protected branch (#{@protected_branches.size})
%p.settings-message.text-center
There are currently no protected branches, protect a branch with the form above.
- else
- can_admin_project = can?(current_user, :admin_project, @project)
%table.table.protected-branches-list
%colgroup
%col{ width: "20%" }
%col{ width: "30%" }
%col{ width: "25%" }
%col{ width: "25%" }
%thead
%tr
%th Branch
%th Last commit
%th Allowed to merge
%th Allowed to push
- if can_admin_project
%th
%tbody
= render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
%table.table.table-bordered
%colgroup
%col{ width: "25%" }
%col{ width: "30%" }
%col{ width: "25%" }
%col{ width: "20%" }
%thead
%tr
%th Protected branch (#{@protected_branches.size})
%th Last commit
%th Allowed to merge
%th Allowed to push
- if can_admin_project
%th
%tbody
= render partial: @protected_branches, locals: { can_admin_project: can_admin_project }
= paginate @protected_branches, theme: 'gitlab'
= paginate @protected_branches, theme: 'gitlab'

View file

@ -0,0 +1,36 @@
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
.panel.panel-default
.panel-heading
%h3.panel-title
Protect a branch
.panel-body
.form-horizontal
.form-group
= f.label :name, class: 'col-md-2 text-right' do
Branch:
.col-md-10
= render partial: "dropdown", locals: { f: f }
.help-block
= link_to 'Wildcards', help_page_path('user/project/protected_branches', anchor: 'wildcard-protected-branches')
such as
%code *-stable
or
%code production/*
are supported
.form-group
%label.col-md-2.text-right{ for: 'merge_access_level_attributes' }
Allowed to merge:
.col-md-10
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-merge wide',
data: { field_name: 'protected_branch[merge_access_level_attributes][access_level]', input_id: 'merge_access_level_attributes' }})
.form-group
%label.col-md-2.text-right{ for: 'push_access_level_attributes' }
Allowed to push:
.col-md-10
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-push wide',
data: { field_name: 'protected_branch[push_access_level_attributes][access_level]', input_id: 'push_access_level_attributes' }})
.panel-footer
= f.submit 'Protect', class: 'btn-create btn', disabled: true

View file

@ -1,17 +1,15 @@
= f.hidden_field(:name)
= dropdown_tag("Protected Branch",
options: { title: "Pick protected branch", toggle_class: 'js-protected-branch-select js-filter-submit',
= dropdown_tag('Select branch or create wildcard',
options: { toggle_class: 'js-protected-branch-select js-filter-submit wide',
filter: true, dropdown_class: "dropdown-menu-selectable", placeholder: "Search protected branches",
footer_content: true,
data: { show_no: true, show_any: true, show_upcoming: true,
selected: params[:protected_branch_name],
project_id: @project.try(:id) } }) do
%ul.dropdown-footer-list.hidden.protected-branch-select-footer-list
%ul.dropdown-footer-list
%li
= link_to '#', title: "New Protected Branch", class: "create-new-protected-branch" do
Create new
:javascript
new ProtectedBranchSelect();
Create wildcard
%code

View file

@ -1,5 +1,4 @@
- url = namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
%tr
%tr.js-protected-branch-edit-form{ data: { url: namespace_project_protected_branch_path(@project.namespace, @project, protected_branch), branch_id: protected_branch.id } }
%td
= protected_branch.name
- if @project.root_ref?(protected_branch.name)
@ -16,14 +15,14 @@
(branch was removed from repository)
%td
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level
= dropdown_tag(protected_branch.merge_access_level.humanize,
options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable merge',
data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "merge_access_level" }})
= dropdown_tag( (protected_branch.merge_access_level.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container',
data: { field_name: "allowed_to_merge_#{protected_branch.id}" }})
%td
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level
= dropdown_tag(protected_branch.push_access_level.humanize,
options: { title: "Allowed to push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable push',
data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "push_access_level" }})
= dropdown_tag( (protected_branch.push_access_level.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container',
data: { field_name: "allowed_to_push_#{protected_branch.id}" }})
- if can_admin_project
%td
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right"
= link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning'

View file

@ -14,41 +14,7 @@
%li prevent <strong>anyone</strong> from deleting the branch
%p.append-bottom-0 Read more about #{link_to "protected branches", help_page_path("user/project/protected_branches"), class: "underlined-link"} and #{link_to "project permissions", help_page_path("user/permissions"), class: "underlined-link"}.
.col-lg-9
%h5.prepend-top-0
Protect a branch
- if can? current_user, :admin_project, @project
= form_for [@project.namespace.becomes(Namespace), @project, @protected_branch] do |f|
= form_errors(@protected_branch)
= render 'create_protected_branch'
.form-group
= f.label :name, "Branch", class: "label-light"
= render partial: "dropdown", locals: { f: f }
%p.help-block
= link_to "Wildcards", help_page_path('user/project/protected_branches', anchor: "wildcard-protected-branches")
such as
%code *-stable
or
%code production/*
are supported.
.form-group
= hidden_field_tag 'protected_branch[merge_access_level_attributes][access_level]'
= label_tag "Allowed to merge: ", nil, class: "label-light append-bottom-0"
= dropdown_tag("<Make a selection>",
options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge',
dropdown_class: 'dropdown-menu-selectable',
data: { field_name: "protected_branch[merge_access_level_attributes][access_level]" }})
.form-group
= hidden_field_tag 'protected_branch[push_access_level_attributes][access_level]'
= label_tag "Allowed to push: ", nil, class: "label-light append-bottom-0"
= dropdown_tag("<Make a selection>",
options: { title: "Allowed to push", toggle_class: 'allowed-to-push',
dropdown_class: 'dropdown-menu-selectable',
data: { field_name: "protected_branch[push_access_level_attributes][access_level]" }})
= f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true
%hr
= render "branches_list"

View file

@ -11,7 +11,7 @@ feature 'Projected Branches', feature: true, js: true do
def set_protected_branch_name(branch_name)
find(".js-protected-branch-select").click
find(".dropdown-input-field").set(branch_name)
click_on "Create Protected Branch: #{branch_name}"
click_on("Create wildcard #{branch_name}")
end
describe "explicit protected branches" do
@ -90,7 +90,7 @@ feature 'Projected Branches', feature: true, js: true do
visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('master')
within('.new_protected_branch') do
find(".allowed-to-push").click
find(".js-allowed-to-push").click
within(".dropdown.open .dropdown-menu") { click_on access_type_name }
end
click_on "Protect"
@ -107,8 +107,8 @@ feature 'Projected Branches', feature: true, js: true do
expect(ProtectedBranch.count).to eq(1)
within(".protected-branches-list") do
find(".allowed-to-push").click
within('.dropdown-menu.push') { click_on access_type_name }
find(".js-allowed-to-push").click
within('.js-allowed-to-push-container') { click_on access_type_name }
end
wait_for_ajax
@ -121,7 +121,7 @@ feature 'Projected Branches', feature: true, js: true do
visit namespace_project_protected_branches_path(project.namespace, project)
set_protected_branch_name('master')
within('.new_protected_branch') do
find(".allowed-to-merge").click
find(".js-allowed-to-merge").click
within(".dropdown.open .dropdown-menu") { click_on access_type_name }
end
click_on "Protect"
@ -138,8 +138,8 @@ feature 'Projected Branches', feature: true, js: true do
expect(ProtectedBranch.count).to eq(1)
within(".protected-branches-list") do
find(".allowed-to-merge").click
within('.dropdown-menu.merge') { click_on access_type_name }
find(".js-allowed-to-merge").click
within('.js-allowed-to-merge-container') { click_on access_type_name }
end
wait_for_ajax