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 arrow-parens, no-param-reassign, object-shorthand, no-else-return, comma-dangle, no-underscore-dangle, no-continue, no-restricted-syntax, guard-for-in, no-new, class-methods-use-this, consistent-return, max-len */
/* eslint-disable no-underscore-dangle, class-methods-use-this */
/* global Flash */
(global => {
global.gl = global.gl || {};
import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants';
const PUSH_ACCESS_LEVEL = 'push_access_levels';
const LEVEL_TYPES = {
ROLE: 'role',
USER: 'user',
GROUP: 'group'
};
gl.ProtectedBranchAccessDropdown = class {
export default class ProtectedBranchAccessDropdown {
constructor(options) {
const self = this;
const {
$dropdown,
onSelect,
onHide,
accessLevel,
accessLevelsData
accessLevelsData,
} = options;
this.isAllowedToPushDropdown = false;
this.options = options;
this.groups = [];
this.accessLevel = accessLevel;
this.accessLevelsData = accessLevelsData.roles;
this.$dropdown = $dropdown;
this.$wrap = this.$dropdown.closest(`.${this.accessLevel}-container`);
this.$protectedTagsContainer = $('.js-protected-branches-container');
this.usersPath = '/autocomplete/users.json';
this.groupsPath = '/autocomplete/project_groups.json';
this.defaultLabel = this.$dropdown.data('defaultLabel');
......@@ -35,17 +24,19 @@
this.setSelectedItems([]);
this.persistPreselectedItems();
if (PUSH_ACCESS_LEVEL === this.accessLevel) {
this.isAllowedToPushDropdown = true;
this.noOneObj = this.accessLevelsData[2];
this.noOneObj = this.accessLevelsData.find(level => level.id === ACCESS_LEVEL_NONE);
this.initDropdown();
}
$dropdown.glDropdown({
initDropdown() {
const { onSelect, onHide } = this.options;
this.$dropdown.glDropdown({
data: this.getData.bind(this),
selectable: true,
filterable: true,
filterRemote: true,
data: this.getData.bind(this),
multiSelect: $dropdown.hasClass('js-multiselect'),
multiSelect: this.$dropdown.hasClass('js-multiselect'),
renderRow: this.renderRow.bind(this),
toggleLabel: this.toggleLabel.bind(this),
hidden() {
......@@ -53,69 +44,69 @@
onHide();
}
},
clicked(opts) {
const { $el, e } = opts;
const item = opts.selectedObj;
clicked: (options) => {
const { $el, e } = options;
const item = options.selectedObj;
e.preventDefault();
if ($el.is('.is-active')) {
if (self.isAllowedToPushDropdown) {
if (item.id === self.noOneObj.id) {
if (item.id === this.noOneObj.id) {
// remove all others selected items
self.accessLevelsData.forEach((level) => {
this.accessLevelsData.forEach((level) => {
if (level.id !== item.id) {
self.removeSelectedItem(level);
this.removeSelectedItem(level);
}
});
// remove selected item visually
self.$wrap.find(`.item-${item.type}`).removeClass('is-active');
this.$wrap.find(`.item-${item.type}`).removeClass('is-active');
} else {
const $noOne = self.$wrap.find(`.is-active.item-${item.type}:contains('No one')`);
const $noOne = this.$wrap.find(`.is-active.item-${item.type}[data-role-id="${this.noOneObj.id}"]`);
if ($noOne.length) {
$noOne.removeClass('is-active');
self.removeSelectedItem(self.noOneObj);
this.removeSelectedItem(this.noOneObj);
}
}
// make element active right away
$el.addClass(`is-active item-${item.type}`);
}
// Add "No one"
self.addSelectedItem(item);
this.addSelectedItem(item);
} else {
self.removeSelectedItem(item);
this.removeSelectedItem(item);
}
if (onSelect) {
onSelect(item, $el, self);
}
onSelect(item, $el, this);
}
},
});
}
persistPreselectedItems() {
const itemsToPreselect = this.$dropdown.data('preselectedItems');
if (typeof itemsToPreselect === 'undefined' || !itemsToPreselect.length) {
if (!itemsToPreselect || !itemsToPreselect.length) {
return;
}
itemsToPreselect.forEach((item) => {
item.persisted = true;
const persistedItems = itemsToPreselect.map((item) => {
const persistedItem = Object.assign({}, item);
persistedItem.persisted = true;
return persistedItem;
});
this.setSelectedItems(itemsToPreselect);
this.setSelectedItems(persistedItems);
}
setSelectedItems(items) {
this.items = items.length ? items : [];
setSelectedItems(items = []) {
this.items = items;
}
getSelectedItems() {
return this.items.filter((item) => !item._destroy);
return this.items.filter(item => !item._destroy);
}
getAllSelectedItems() {
......@@ -124,10 +115,9 @@
// Return dropdown as input data ready to submit
getInputData() {
const accessLevels = [];
const selectedItems = this.getAllSelectedItems();
selectedItems.forEach((item) => {
const accessLevels = selectedItems.map((item) => {
const obj = {};
if (typeof item.id !== 'undefined') {
......@@ -146,7 +136,7 @@
obj.group_id = item.group_id;
}
accessLevels.push(obj);
return obj;
});
return accessLevels;
......@@ -159,12 +149,27 @@
let index = -1;
const selectedItems = this.getAllSelectedItems();
for (let i = 0; i < selectedItems.length; i += 1) {
if (selectedItem.id === selectedItems[i].access_level) {
// Compare IDs based on selectedItem.type
selectedItems.forEach((item, i) => {
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;
continue;
}
}
});
if (index !== -1 && selectedItems[index]._destroy) {
delete selectedItems[index]._destroy;
......@@ -179,17 +184,17 @@
name: selectedItem.name || '_name1',
username: selectedItem.username || '_username1',
avatar_url: selectedItem.avatar_url || '_avatar_url1',
type: LEVEL_TYPES.USER
type: LEVEL_TYPES.USER,
};
} else if (selectedItem.type === LEVEL_TYPES.ROLE) {
itemToAdd = {
access_level: selectedItem.id,
type: LEVEL_TYPES.ROLE
type: LEVEL_TYPES.ROLE,
};
} else if (selectedItem.type === LEVEL_TYPES.GROUP) {
itemToAdd = {
group_id: selectedItem.id,
type: LEVEL_TYPES.GROUP
type: LEVEL_TYPES.GROUP,
};
}
......@@ -201,23 +206,25 @@
const selectedItems = this.getAllSelectedItems();
// To find itemToDelete on selectedItems, first we need the index
for (let i = 0; i < selectedItems.length; i += 1) {
const currentItem = selectedItems[i];
if (currentItem.type !== itemToDelete.type) {
continue;
selectedItems.every((item, i) => {
if (item.type !== itemToDelete.type) {
return true;
}
if (currentItem.type === LEVEL_TYPES.USER && currentItem.user_id === itemToDelete.id) {
if (item.type === LEVEL_TYPES.USER &&
item.user_id === itemToDelete.id) {
index = i;
} else if (currentItem.type === LEVEL_TYPES.ROLE && currentItem.access_level === itemToDelete.id) {
} else if (item.type === LEVEL_TYPES.ROLE &&
item.access_level === itemToDelete.id) {
index = i;
} else if (currentItem.type === LEVEL_TYPES.GROUP && currentItem.group_id === itemToDelete.id) {
} else if (item.type === LEVEL_TYPES.GROUP &&
item.group_id === itemToDelete.id) {
index = i;
}
if (index > -1) { break; }
}
// Break once we have index set
return !(index > -1);
});
// if ItemToDelete is not really selected do nothing
if (index === -1) {
......@@ -238,17 +245,17 @@
toggleLabel() {
const currentItems = this.getSelectedItems();
const types = _.groupBy(currentItems, (item) => item.type);
const label = [];
const types = _.groupBy(currentItems, item => item.type);
let label = [];
if (currentItems.length) {
for (const LEVEL_TYPE in LEVEL_TYPES) {
const typeName = LEVEL_TYPES[LEVEL_TYPE];
label = Object.keys(LEVEL_TYPES).map((levelType) => {
const typeName = LEVEL_TYPES[levelType];
const numberOfTypes = types[typeName] ? types[typeName].length : 0;
const text = numberOfTypes === 1 ? typeName : `${typeName}s`;
label.push(`${numberOfTypes} ${text}`);
}
return `${numberOfTypes} ${text}`;
});
} else {
label.push(this.defaultLabel);
}
......@@ -259,27 +266,25 @@
}
getData(query, callback) {
this.getUsers(query).done((usersResponse) => {
this.getUsers(query)
.done((usersResponse) => {
if (this.groups.length) {
callback(this.consolidateData(usersResponse, this.groups));
} else {
this.getGroups(query).done((groupsResponse) => {
this.getGroups(query)
.done((groupsResponse) => {
// Cache groups to avoid multiple requests
this.groups = groupsResponse;
callback(this.consolidateData(usersResponse, groupsResponse));
});
})
.error(() => new Flash('Failed to load groups.'));
}
}).error(() => {
new Flash('Failed to load users.');
});
}).error(() => new Flash('Failed to load users.'));
}
consolidateData(usersResponse, groupsResponse) {
let consolidatedData = [];
const map = [];
let roles = [];
const users = [];
let groups = [];
const selectedItems = this.getSelectedItems();
// ID property is handled differently locally from the server
......@@ -299,15 +304,18 @@
/*
* Build groups
*/
groups = groupsResponse.map((group) => {
group.type = LEVEL_TYPES.GROUP;
return group;
});
const groups = groupsResponse.map(group => ({ ...group, type: LEVEL_TYPES.GROUP }));
/*
* Build roles
*/
roles = this.accessLevelsData.map((level) => {
const roles = this.accessLevelsData.map((level) => {
/* eslint-disable no-param-reassign */
// This re-assignment is intentional as
// level.type property is being used in removeSelectedItem()
// for comparision, and accessLevelsData is provided by
// gon.create_access_levels which doesn't have `type` included.
// See this discussion https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1629#note_31285823
level.type = LEVEL_TYPES.ROLE;
return level;
});
......@@ -315,38 +323,32 @@
/*
* Build users
*/
for (let x = 0; x < selectedItems.length; x += 1) {
const current = selectedItems[x];
if (current.type !== LEVEL_TYPES.USER) { continue; }
const users = selectedItems.filter(item => item.type === LEVEL_TYPES.USER).map((item) => {
// Save identifiers for easy-checking more later
map.push(LEVEL_TYPES.USER + item.user_id);
// Collect selected users
users.push({
id: current.user_id,
name: current.name,
username: current.username,
avatar_url: current.avatar_url,
return {
id: item.user_id,
name: item.name,
username: item.username,
avatar_url: item.avatar_url,
type: LEVEL_TYPES.USER,
};
});
// Save identifiers for easy-checking more later
map.push(LEVEL_TYPES.USER + current.user_id);
}
// Has to be checked against server response
// because the selected item can be in filter results
for (let i = 0; i < usersResponse.length; i += 1) {
const u = usersResponse[i];
usersResponse.forEach((response) => {
// Add is it has not been added
if (map.indexOf(LEVEL_TYPES.USER + u.id) === -1) {
u.type = LEVEL_TYPES.USER;
users.push(u);
}
if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) {
const user = Object.assign({}, response);
user.type = LEVEL_TYPES.USER;
users.push(user);
}
});
if (roles.length) {
consolidatedData = consolidatedData.concat([{ header: 'Roles', }], roles);
consolidatedData = consolidatedData.concat([{ header: 'Roles' }], roles);
}
if (groups.length) {
......@@ -354,11 +356,11 @@
consolidatedData = consolidatedData.concat(['divider']);
}
consolidatedData = consolidatedData.concat([{ header: 'Groups', }], groups);
consolidatedData = consolidatedData.concat([{ header: 'Groups' }], groups);
}
if (users.length) {
consolidatedData = consolidatedData.concat(['divider'], [{ header: 'Users', }], users);
consolidatedData = consolidatedData.concat(['divider'], [{ header: 'Users' }], users);
}
return consolidatedData;
......@@ -367,73 +369,112 @@
getUsers(query) {
return $.ajax({
dataType: 'json',
url: this.buildUrl(this.usersPath),
url: this.buildUrl(gon.relative_url_root, this.usersPath),
data: {
search: query,
per_page: 20,
active: true,
project_id: gon.current_project_id,
push_code: true,
}
},
});
}
getGroups() {
return $.ajax({
dataType: 'json',
url: this.buildUrl(this.groupsPath),
url: this.buildUrl(gon.relative_url_root, this.groupsPath),
data: {
project_id: gon.current_project_id
}
project_id: gon.current_project_id,
},
});
}
buildUrl(url) {
if (gon.relative_url_root != null) {
url = gon.relative_url_root.replace(/\/$/, '') + url;
buildUrl(urlRoot, url) {
let newUrl;
if (urlRoot != null) {
newUrl = urlRoot.replace(/\/$/, '') + url;
}
return url;
return newUrl;
}
renderRow(item) {
let criteria = {};
let groupRowEl;
// Dectect if the current item is already saved so we can add
// the `is-active` class so the item looks as marked
if (item.type === LEVEL_TYPES.USER) {
switch (item.type) {
case LEVEL_TYPES.USER:
criteria = { user_id: item.id };
} else if (item.type === LEVEL_TYPES.ROLE) {
break;
case LEVEL_TYPES.ROLE:
criteria = { access_level: item.id };
} else if (item.type === LEVEL_TYPES.GROUP) {
break;
case LEVEL_TYPES.GROUP:
criteria = { group_id: item.id };
break;
default:
break;
}
const isActive = _.findWhere(this.getSelectedItems(), criteria) ? 'is-active' : '';
if (item.type === LEVEL_TYPES.USER) {
return this.userRowHtml(item, isActive);
} else if (item.type === LEVEL_TYPES.ROLE) {
return this.roleRowHtml(item, isActive);
} else if (item.type === LEVEL_TYPES.GROUP) {
return this.groupRowHtml(item, isActive);
switch (item.type) {
case LEVEL_TYPES.USER:
groupRowEl = this.userRowHtml(item, isActive);
break;
case LEVEL_TYPES.ROLE:
groupRowEl = this.roleRowHtml(item, isActive);
break;
case LEVEL_TYPES.GROUP:
groupRowEl = this.groupRowHtml(item, isActive);
break;
default:
groupRowEl = '';
break;
}
return groupRowEl;
}
userRowHtml(user, isActive) {
const avatarHtml = `<img src='${user.avatar_url}' class='avatar avatar-inline' width='30'>`;
const nameHtml = `<strong class='dropdown-menu-user-full-name'>${user.name}</strong>`;
const usernameHtml = `<span class='dropdown-menu-user-username'>${user.username}</span>`;
return `<li><a href='#' class='${isActive ? 'is-active' : ''}'>${avatarHtml} ${nameHtml} ${usernameHtml}</a></li>`;
const isActiveClass = isActive || '';
return `
<li>
<a href="#" class="${isActiveClass}">
<img src="${user.avatar_url}" class="avatar avatar-inline" width="30">
<strong class="dropdown-menu-user-full-name">${user.name}</strong>
<span class="dropdown-menu-user-username">${user.username}</span>
</a>
</li>
`;
}
groupRowHtml(group, isActive) {
const avatarHtml = group.avatar_url ? `<img src='${group.avatar_url}' class='avatar avatar-inline' width='30'>` : '';
const groupnameHtml = `<span class='dropdown-menu-group-groupname'>${group.name}</span>`;
return `<li><a href='#' class='${isActive ? 'is-active' : ''}'>${avatarHtml} ${groupnameHtml}</a></li>`;
const isActiveClass = isActive || '';
const avatarEl = group.avatar_url ? `<img src="${group.avatar_url}" class="avatar avatar-inline" width="30">` : '';
return `
<li>
<a href="#" class="${isActiveClass}">
${avatarEl}
<span class="dropdown-menu-group-groupname">${group.name}</span>
</a>
</li>
`;
}
roleRowHtml(role, isActive) {
return `<li><a href='#' class='${isActive ? 'is-active' : ''} item-${role.type}'>${role.text}</a></li>`;
const isActiveClass = isActive || '';
return `
<li>
<a href="#" class="${isActiveClass} item-${role.type}" data-role-id="${role.id}">
${role.text}
</a>
</li>
`;
}
};
})(window);
}
/* 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