Commit 1de21943 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch 'ee-31031-convert-protected-branches-es6' into 'master'

EE - Convert Protected Branches feature JS code to ES6

See merge request !2481
parents 69067637 1067010e
export const ACCESS_LEVELS = {
MERGE: 'merge_access_levels',
PUSH: 'push_access_levels',
};
export const LEVEL_TYPES = {
ROLE: 'role',
USER: 'user',
GROUP: 'group',
};
export const LEVEL_ID_PROP = {
ROLE: 'access_level',
USER: 'user_id',
GROUP: 'group_id',
};
export const ACCESS_LEVEL_NONE = 0;
/* eslint-disable no-unused-vars */
import './protected_branch_access_dropdown';
import './protected_branch_create';
import './protected_branch_dropdown';
import './protected_branch_edit';
import './protected_branch_edit_list';
import ProtectedBranchCreate from './protected_branch_create';
import ProtectedBranchEditList from './protected_branch_edit_list';
$(() => {
const protectedBranchCreate = new gl.ProtectedBranchCreate();
const protectedBranchEditList = new gl.ProtectedBranchEditList();
const protectedBranchCreate = new ProtectedBranchCreate();
const protectedBranchEditList = new ProtectedBranchEditList();
});
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, guard-for-in, no-restricted-syntax, max-len */
/* global ProtectedBranchDropdown */
/* global Flash */
(global => {
global.gl = global.gl || {};
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import ProtectedBranchDropdown from './protected_branch_dropdown';
const ACCESS_LEVELS = {
MERGE: 'merge_access_levels',
PUSH: 'push_access_levels',
};
const LEVEL_TYPES = {
ROLE: 'role',
USER: 'user',
GROUP: 'group'
};
gl.ProtectedBranchCreate = class {
export default class ProtectedBranchCreate {
constructor() {
this.$wrap = this.$form = $('.js-new-protected-branch');
this.$form = $('.js-new-protected-branch');
this.buildDropdowns();
this.$branchInput = this.$wrap.find('input[name="protected_branch[name]"]');
this.$branchInput = this.$form.find('input[name="protected_branch[name]"]');
this.bindEvents();
}
......@@ -29,32 +17,31 @@
}
buildDropdowns() {
const $allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push');
// Cache callback
this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Merge dropdown
this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new gl.ProtectedBranchAccessDropdown({
this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new ProtectedBranchAccessDropdown({
$dropdown: $allowedToMergeDropdown,
accessLevelsData: gon.merge_access_levels,
onSelect: this.onSelectCallback,
accessLevel: ACCESS_LEVELS.MERGE
accessLevel: ACCESS_LEVELS.MERGE,
});
// Allowed to Push dropdown
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new gl.ProtectedBranchAccessDropdown({
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new ProtectedBranchAccessDropdown({
$dropdown: $allowedToPushDropdown,
accessLevelsData: gon.push_access_levels,
onSelect: this.onSelectCallback,
accessLevel: ACCESS_LEVELS.PUSH
accessLevel: ACCESS_LEVELS.PUSH,
});
// Protected branch dropdown
new window.ProtectedBranchDropdown({
$dropdown: this.$wrap.find('.js-protected-branch-select'),
onSelect: this.onSelectCallback
this.protectedBranchDropdown = new ProtectedBranchDropdown({
$dropdown: this.$form.find('.js-protected-branch-select'),
onSelect: this.onSelectCallback,
});
}
......@@ -62,7 +49,7 @@
onSelect() {
const $allowedToMerge = this[`${ACCESS_LEVELS.MERGE}_dropdown`].getSelectedItems();
const $allowedToPush = this[`${ACCESS_LEVELS.PUSH}_dropdown`].getSelectedItems();
const toggle = !(this.$wrap.find('input[name="protected_branch[name]"]').val() && $allowedToMerge.length && $allowedToPush.length);
const toggle = !(this.$form.find('input[name="protected_branch[name]"]').val() && $allowedToMerge.length && $allowedToPush.length);
this.$form.find('input[type="submit"]').attr('disabled', toggle);
}
......@@ -71,34 +58,33 @@
const formData = {
authenticity_token: this.$form.find('input[name="authenticity_token"]').val(),
protected_branch: {
name: this.$wrap.find('input[name="protected_branch[name]"]').val(),
}
name: this.$form.find('input[name="protected_branch[name]"]').val(),
},
};
for (const ACCESS_LEVEL in ACCESS_LEVELS) {
const selectedItems = this[`${ACCESS_LEVELS[ACCESS_LEVEL]}_dropdown`].getSelectedItems();
Object.keys(ACCESS_LEVELS).forEach((level) => {
const accessLevel = ACCESS_LEVELS[level];
const selectedItems = this[`${accessLevel}_dropdown`].getSelectedItems();
const levelAttributes = [];
for (let i = 0; i < selectedItems.length; i += 1) {
const current = selectedItems[i];
if (current.type === LEVEL_TYPES.USER) {
selectedItems.forEach((item) => {
if (item.type === LEVEL_TYPES.USER) {
levelAttributes.push({
user_id: selectedItems[i].user_id
user_id: item.user_id,
});
} else if (current.type === LEVEL_TYPES.ROLE) {
} else if (item.type === LEVEL_TYPES.ROLE) {
levelAttributes.push({
access_level: selectedItems[i].access_level
access_level: item.access_level,
});
} else if (current.type === LEVEL_TYPES.GROUP) {
} else if (item.type === LEVEL_TYPES.GROUP) {
levelAttributes.push({
group_id: selectedItems[i].group_id
group_id: item.group_id,
});
}
}
});
formData.protected_branch[`${ACCESS_LEVELS[ACCESS_LEVEL]}_attributes`] = levelAttributes;
}
formData.protected_branch[`${accessLevel}_attributes`] = levelAttributes;
});
return formData;
}
......@@ -109,14 +95,11 @@
$.ajax({
url: this.$form.attr('action'),
method: this.$form.attr('method'),
data: this.getFormData()
data: this.getFormData(),
})
.success(() => {
location.reload();
})
.fail(() => {
new Flash('Failed to protect the branch');
});
.fail(() => new Flash('Failed to protect the branch'));
}
};
})(window);
}
/* eslint-disable comma-dangle, no-unused-vars */
class ProtectedBranchDropdown {
export default class ProtectedBranchDropdown {
/**
* @param {Object} options containing
* `$dropdown` target element
* `onSelect` event callback
* $dropdown must be an element created using `dropdown_tag()` rails helper
*/
constructor(options) {
this.onSelect = options.onSelect;
this.$dropdown = options.$dropdown;
......@@ -12,7 +16,7 @@ class ProtectedBranchDropdown {
this.bindEvents();
// Hide footer
this.$dropdownFooter.addClass('hidden');
this.toggleFooter(true);
}
buildDropdown() {
......@@ -21,7 +25,7 @@ class ProtectedBranchDropdown {
filterable: true,
remote: false,
search: {
fields: ['title']
fields: ['title'],
},
selectable: true,
toggleLabel(selected) {
......@@ -36,10 +40,9 @@ class ProtectedBranchDropdown {
},
onFilter: this.toggleCreateNewButton.bind(this),
clicked: (options) => {
const { $el, e } = options;
e.preventDefault();
options.e.preventDefault();
this.onSelect();
}
},
});
}
......@@ -64,20 +67,22 @@ class ProtectedBranchDropdown {
}
toggleCreateNewButton(branchName) {
if (branchName) {
this.selectedBranch = {
title: branchName,
id: branchName,
text: branchName
text: branchName,
};
if (branchName) {
this.$dropdownContainer
.find('.js-create-new-protected-branch code')
.text(branchName);
}
this.$dropdownFooter.toggleClass('hidden', !branchName);
this.toggleFooter(!branchName);
}
}
window.ProtectedBranchDropdown = ProtectedBranchDropdown;
toggleFooter(toggleState) {
this.$dropdownFooter.toggleClass('hidden', toggleState);
}
}
/* eslint-disable no-new, arrow-parens, no-param-reassign, comma-dangle, dot-notation, no-unused-vars, no-restricted-syntax, guard-for-in, max-len */
/* eslint-disable no-new */
/* global Flash */
(global => {
global.gl = global.gl || {};
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
const ACCESS_LEVELS = {
MERGE: 'merge_access_levels',
PUSH: 'push_access_levels',
};
const LEVEL_TYPES = {
ROLE: 'role',
USER: 'user',
GROUP: 'group'
};
gl.ProtectedBranchEdit = class {
export default class ProtectedBranchEdit {
constructor(options) {
this.$wraps = {};
this.hasChanges = false;
......@@ -31,44 +20,46 @@
buildDropdowns() {
// Allowed to merge dropdown
this['merge_access_levels_dropdown'] = new gl.ProtectedBranchAccessDropdown({
this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new ProtectedBranchAccessDropdown({
accessLevel: ACCESS_LEVELS.MERGE,
accessLevelsData: gon.merge_access_levels,
$dropdown: this.$allowedToMergeDropdown,
onSelect: this.onSelectOption.bind(this),
onHide: this.onDropdownHide.bind(this)
onHide: this.onDropdownHide.bind(this),
});
// Allowed to push dropdown
this['push_access_levels_dropdown'] = new gl.ProtectedBranchAccessDropdown({
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new ProtectedBranchAccessDropdown({
accessLevel: ACCESS_LEVELS.PUSH,
accessLevelsData: gon.push_access_levels,
$dropdown: this.$allowedToPushDropdown,
onSelect: this.onSelectOption.bind(this),
onHide: this.onDropdownHide.bind(this)
onHide: this.onDropdownHide.bind(this),
});
}
onSelectOption(item, $el, dropdownInstance) {
onSelectOption() {
this.hasChanges = true;
}
onDropdownHide() {
if (!this.hasChanges) return;
if (!this.hasChanges) {
return;
}
this.hasChanges = true;
this.updatePermissions();
}
updatePermissions() {
const formData = {};
const formData = Object.keys(ACCESS_LEVELS).reduce((acc, level) => {
/* eslint-disable no-param-reassign */
const accessLevelName = ACCESS_LEVELS[level];
const inputData = this[`${accessLevelName}_dropdown`].getInputData(accessLevelName);
acc[`${accessLevelName}_attributes`] = inputData;
for (const ACCESS_LEVEL in ACCESS_LEVELS) {
const accessLevelName = ACCESS_LEVELS[ACCESS_LEVEL];
formData[`${accessLevelName}_attributes`] = this[`${accessLevelName}_dropdown`].getInputData(accessLevelName);
}
return acc;
}, {});
return $.ajax({
type: 'POST',
......@@ -76,22 +67,21 @@
dataType: 'json',
data: {
_method: 'PATCH',
protected_branch: formData
protected_branch: formData,
},
success: (response) => {
this.hasChanges = false;
for (const ACCESS_LEVEL in ACCESS_LEVELS) {
const accessLevelName = ACCESS_LEVELS[ACCESS_LEVEL];
Object.keys(ACCESS_LEVELS).forEach((level) => {
const accessLevelName = ACCESS_LEVELS[level];
// The data coming from server will be the new persisted *state* for each dropdown
this.setSelectedItemsToDropdown(response[accessLevelName], `${accessLevelName}_dropdown`);
}
});
},
error() {
$.scrollTo(0);
new Flash('Failed to update branch!');
}
new Flash('Failed to update branch!', null, $('.js-protected-branches-list'));
},
}).always(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
......@@ -99,47 +89,39 @@
}
setSelectedItemsToDropdown(items = [], dropdownName) {
const itemsToAdd = [];
for (let i = 0; i < items.length; i += 1) {
let itemToAdd;
const currentItem = items[i];
const itemsToAdd = items.map((currentItem) => {
if (currentItem.user_id) {
// Do this only for users for now
// get the current data for selected items
const selectedItems = this[dropdownName].getSelectedItems();
const currentSelectedItem = _.findWhere(selectedItems, { user_id: currentItem.user_id });
itemToAdd = {
return {
id: currentItem.id,
user_id: currentItem.user_id,
type: LEVEL_TYPES.USER,
persisted: true,
name: currentSelectedItem.name,
username: currentSelectedItem.username,
avatar_url: currentSelectedItem.avatar_url
avatar_url: currentSelectedItem.avatar_url,
};
} else if (currentItem.group_id) {
itemToAdd = {
return {
id: currentItem.id,
group_id: currentItem.group_id,
type: LEVEL_TYPES.GROUP,
persisted: true
persisted: true,
};
} else {
itemToAdd = {
}
return {
id: currentItem.id,
access_level: currentItem.access_level,
type: LEVEL_TYPES.ROLE,
persisted: true
persisted: true,
};
}
itemsToAdd.push(itemToAdd);
}
});
this[dropdownName].setSelectedItems(itemsToAdd);
}
};
})(window);
}
/* eslint-disable arrow-parens, no-param-reassign, no-new, comma-dangle */
/* eslint-disable no-new */
(global => {
global.gl = global.gl || {};
import ProtectedBranchEdit from './protected_branch_edit';
gl.ProtectedBranchEditList = class {
export default class ProtectedBranchEditList {
constructor() {
this.$wrap = $('.protected-branches-list');
this.initEditForm();
}
initEditForm() {
// Build edit forms
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new gl.ProtectedBranchEdit({
$wrap: $(el)
new ProtectedBranchEdit({
$wrap: $(el),
});
});
}
};
})(window);
}
......@@ -8,4 +8,10 @@ export const LEVEL_TYPES = {
GROUP: 'group',
};
export const LEVEL_ID_PROP = {
ROLE: 'access_level',
USER: 'user_id',
GROUP: 'group_id',
};
export const ACCESS_LEVEL_NONE = 0;
/* eslint-disable no-underscore-dangle, class-methods-use-this */
/* global Flash */
import { ACCESS_LEVELS, LEVEL_TYPES, ACCESS_LEVEL_NONE } from './constants';
import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants';
export default class ProtectedTagAccessDropdown {
constructor(options) {
......@@ -11,7 +11,6 @@ export default class ProtectedTagAccessDropdown {
accessLevelsData,
} = options;
this.options = options;
this.isAllowedToCreateDropdown = false;
this.groups = [];
this.accessLevel = accessLevel;
this.accessLevelsData = accessLevelsData.roles;
......@@ -25,10 +24,7 @@ export default class ProtectedTagAccessDropdown {
this.setSelectedItems([]);
this.persistPreselectedItems();
if (ACCESS_LEVELS.CREATE === this.accessLevel) {
this.isAllowedToCreateDropdown = true;
this.noOneObj = this.accessLevelsData.find(level => level.id === ACCESS_LEVEL_NONE);
}
this.initDropdown();
}
......@@ -56,7 +52,6 @@ export default class ProtectedTagAccessDropdown {
e.preventDefault();
if ($el.is('.is-active')) {
if (self.isAllowedToCreateDropdown) {
if (item.id === self.noOneObj.id) {
self.accessLevelsData.forEach((level) => {
if (level.id !== item.id) {
......@@ -74,7 +69,6 @@ export default class ProtectedTagAccessDropdown {
}
$el.addClass(`is-active item-${item.type}`);
}
self.addSelectedItem(item);
} else {
......@@ -151,8 +145,24 @@ export default class ProtectedTagAccessDropdown {
let index = -1;
const selectedItems = this.getAllSelectedItems();
// Compare IDs based on selectedItem.type
selectedItems.forEach((item, i) => {
if (selectedItem.id === item.access_level) {
let comparator;
switch (selectedItem.type) {
case LEVEL_TYPES.ROLE:
comparator = LEVEL_ID_PROP.ROLE;
break;
case LEVEL_TYPES.GROUP:
comparator = LEVEL_ID_PROP.GROUP;
break;
case LEVEL_TYPES.USER:
comparator = LEVEL_ID_PROP.USER;
break;
default:
break;
}
if (selectedItem.id === item[comparator]) {
index = i;
}
});
......
......@@ -8,7 +8,7 @@ export default class ProtectedTagCreate {
constructor() {
this.$form = $('.js-new-protected-tag');
this.buildDropdowns();
this.$branchTag = this.$form.find('input[name="protected_tag[name]"]');
this.$tagInput = this.$form.find('input[name="protected_tag[name]"]');
this.bindEvents();
}
......
......@@ -110,7 +110,7 @@ describe 'Branches' do
project.add_user(user, :developer)
end
it 'does not allow devleoper to removes protected branch', js: true do
it 'does not allow devleoper to remove protected branch', js: true do
visit project_branches_path(project)
fill_in 'branch-search', with: 'fix'
......
......@@ -2,7 +2,7 @@ shared_examples "protected branches > access control > EE" do
[['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
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)
roles_except_noone = access_level_class.human_access_levels.except(0)
let(:users) { create_list(:user, 5) }
let(:groups) { create_list(:group, 5) }
......@@ -22,7 +22,7 @@ shared_examples "protected branches > access control > EE" do
set_protected_branch_name('master')
set_allowed_to(git_operation, users.map(&:name))
set_allowed_to(git_operation, groups.map(&:name))
set_allowed_to(git_operation, roles.values)
roles_except_noone.each { |(_, access_type_name)| set_allowed_to(git_operation, access_type_name) }
set_allowed_to(other_git_operation)
click_on "Protect"
......@@ -31,7 +31,7 @@ shared_examples "protected branches > access control > EE" do
expect(ProtectedBranch.count).to eq(1)
access_levels = last_access_levels(git_operation)
roles.each { |(access_type_id, _)| expect(access_levels.map(&:access_level)).to include(access_type_id) }
roles_except_noone.each { |(access_type_id, _)| expect(access_levels.map(&:access_level)).to include(access_type_id) }
users.each { |user| expect(access_levels.map(&:user_id)).to include(user.id) }
groups.each { |group| expect(access_levels.map(&:group_id)).to include(group.id) }
end
......@@ -46,14 +46,14 @@ shared_examples "protected branches > access control > EE" do
set_allowed_to(git_operation, users.map(&:name), form: ".js-protected-branch-edit-form")
set_allowed_to(git_operation, groups.map(&:name), form: ".js-protected-branch-edit-form")
set_allowed_to(git_operation, roles.values, form: ".js-protected-branch-edit-form")
roles_except_noone.each { |(_, access_type_name)| set_allowed_to(git_operation, access_type_name, form: ".js-protected-branch-edit-form") }
wait_for_requests
expect(ProtectedBranch.count).to eq(1)
access_levels = last_access_levels(git_operation)
roles.each { |(access_type_id, _)| expect(access_levels.map(&:access_level)).to include(access_type_id) }
roles_except_noone.each { |(access_type_id, _)| expect(access_levels.map(&:access_level)).to include(access_type_id) }
users.each { |user| expect(access_levels.map(&:user_id)).to include(user.id) }
groups.each { |group| expect(access_levels.map(&:group_id)).to include(group.id) }
end
......@@ -63,7 +63,7 @@ shared_examples "protected branches > access control > EE" do
set_protected_branch_name('master')
users.each { |user| set_allowed_to(git_operation, user.name) }
roles.each { |(_, access_type_name)| set_allowed_to(git_operation, access_type_name) }
roles_except_noone.each { |(_, access_type_name)| set_allowed_to(git_operation, access_type_name) }
groups.each { |group| set_allowed_to(git_operation, group.name) }
set_allowed_to(other_git_operation)
......@@ -71,7 +71,7 @@ shared_examples "protected branches > access control > EE" do
users.each { |user| set_allowed_to(git_operation, user.name, form: ".js-protected-branch-edit-form") }
groups.each { |group| set_allowed_to(git_operation, group.name, form: ".js-protected-branch-edit-form") }
roles.each { |(_, access_type_name)| set_allowed_to(git_operation, access_type_name, form: ".js-protected-branch-edit-form") }
roles_except_noone.each { |(_, access_type_name)| set_allowed_to(git_operation, access_type_name, form: ".js-protected-branch-edit-form") }
wait_for_requests
......@@ -89,7 +89,7 @@ shared_examples "protected branches > access control > EE" do
# Create Protected Branch
set_protected_branch_name('master')
set_allowed_to(git_operation, roles.values)
roles_except_noone.each { |(_, access_type_name)| set_allowed_to(git_operation, access_type_name) }
set_allowed_to(other_git_operation)
click_on 'Protect'
......@@ -119,7 +119,7 @@ shared_examples "protected branches > access control > EE" do
expect(ProtectedBranch.count).to eq(1)
access_levels = last_access_levels(git_operation)
roles.each { |(access_type_id, _)| expect(access_levels.map(&:access_level)).to include(access_type_id) }
roles_except_noone.each { |(access_type_id, _)| expect(access_levels.map(&:access_level)).to include(access_type_id) }
expect(access_levels.map(&:user_id)).to include(users.last.id)
end
end
......
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