Commit 957f7aef authored by Etienne Baqué's avatar Etienne Baqué Committed by Mayra Cabrera

Consolidated FOSS EE ProtectedBranch JS files

Moved ProtectedBranch JS files to FOSS.
Adjusted JS imports accordingly.
parent 444c72b4
......@@ -14,7 +14,7 @@ export default () => {
new ProtectedTagEditList();
initDeployKeys();
initSettingsPanels();
new ProtectedBranchCreate();
new ProtectedBranchCreate({ hasLicense: false });
new ProtectedBranchEditList();
new DueDateSelectors();
fileUpload('.js-choose-file', '.js-object-map-input');
......
......@@ -7,8 +7,9 @@ import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants';
export default class AccessDropdown {
constructor(options) {
const { $dropdown, accessLevel, accessLevelsData } = options;
const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options;
this.options = options;
this.hasLicense = hasLicense;
this.groups = [];
this.accessLevel = accessLevel;
this.accessLevelsData = accessLevelsData.roles;
......@@ -47,9 +48,18 @@ export default class AccessDropdown {
e.preventDefault();
if (!this.hasLicense) {
// We're not multiselecting quite yet with FOSS:
// remove all preselected items before selecting this item
// https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37499
this.accessLevelsData.forEach(level => {
this.removeSelectedItem(level);
});
}
if ($el.is('.is-active')) {
if (this.noOneObj) {
if (item.id === this.noOneObj.id) {
if (item.id === this.noOneObj.id && this.hasLicense) {
// remove all others selected items
this.accessLevelsData.forEach(level => {
if (level.id !== item.id) {
......@@ -286,21 +296,23 @@ export default class AccessDropdown {
}
getData(query, callback) {
Promise.all([
this.getUsers(query),
this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(),
])
.then(([usersResponse, groupsResponse]) => {
this.groupsData = groupsResponse;
callback(this.consolidateData(usersResponse.data, groupsResponse.data));
})
.catch(() => Flash(__('Failed to load groups & users.')));
if (this.hasLicense) {
Promise.all([
this.getUsers(query),
this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(),
])
.then(([usersResponse, groupsResponse]) => {
this.groupsData = groupsResponse;
callback(this.consolidateData(usersResponse.data, groupsResponse.data));
})
.catch(() => Flash(__('Failed to load groups & users.')));
} else {
callback(this.consolidateData());
}
}
consolidateData(usersResponse, groupsResponse) {
consolidateData(usersResponse = [], groupsResponse = []) {
let consolidatedData = [];
const map = [];
const selectedItems = this.getSelectedItems();
// ID property is handled differently locally from the server
//
......@@ -316,14 +328,6 @@ export default class AccessDropdown {
// In dropdown: `id`
// For submit: `user_id`
/*
* Build groups
*/
const groups = groupsResponse.map(group => ({
...group,
type: LEVEL_TYPES.GROUP,
}));
/*
* Build roles
*/
......@@ -338,35 +342,6 @@ export default class AccessDropdown {
return level;
});
/*
* Build users
*/
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);
return {
id: item.user_id,
name: item.name,
username: item.username,
avatar_url: item.avatar_url,
type: LEVEL_TYPES.USER,
};
});
// Has to be checked against server response
// because the selected item can be in filter results
usersResponse.forEach(response => {
// Add is it has not been added
if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) {
const user = { ...response };
user.type = LEVEL_TYPES.USER;
users.push(user);
}
});
if (roles.length) {
consolidatedData = consolidatedData.concat(
[{ type: 'header', content: s__('AccessDropdown|Roles') }],
......@@ -374,23 +349,64 @@ export default class AccessDropdown {
);
}
if (groups.length) {
if (roles.length) {
consolidatedData = consolidatedData.concat([{ type: 'divider' }]);
}
if (this.hasLicense) {
const map = [];
const selectedItems = this.getSelectedItems();
/*
* Build groups
*/
const groups = groupsResponse.map(group => ({
...group,
type: LEVEL_TYPES.GROUP,
}));
/*
* Build users
*/
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);
return {
id: item.user_id,
name: item.name,
username: item.username,
avatar_url: item.avatar_url,
type: LEVEL_TYPES.USER,
};
});
// Has to be checked against server response
// because the selected item can be in filter results
usersResponse.forEach(response => {
// Add is it has not been added
if (map.indexOf(LEVEL_TYPES.USER + response.id) === -1) {
const user = { ...response };
user.type = LEVEL_TYPES.USER;
users.push(user);
}
});
consolidatedData = consolidatedData.concat(
[{ type: 'header', content: s__('AccessDropdown|Groups') }],
groups,
);
}
if (groups.length) {
if (roles.length) {
consolidatedData = consolidatedData.concat([{ type: 'divider' }]);
}
if (users.length) {
consolidatedData = consolidatedData.concat(
[{ type: 'divider' }],
[{ type: 'header', content: s__('AccessDropdown|Users') }],
users,
);
consolidatedData = consolidatedData.concat(
[{ type: 'header', content: s__('AccessDropdown|Groups') }],
groups,
);
}
if (users.length) {
consolidatedData = consolidatedData.concat(
[{ type: 'divider' }],
[{ type: 'header', content: s__('AccessDropdown|Users') }],
users,
);
}
}
return consolidatedData;
......
import { __ } from '~/locale';
export default class ProtectedBranchAccessDropdown {
constructor(options) {
this.options = options;
this.initDropdown();
}
initDropdown() {
const { $dropdown, data, onSelect } = this.options;
$dropdown.glDropdown({
data,
selectable: true,
inputId: $dropdown.data('inputId'),
fieldName: $dropdown.data('fieldName'),
toggleLabel(item, $el) {
if ($el.is('.is-active')) {
return item.text;
}
return __('Select');
},
clicked(options) {
options.e.preventDefault();
onSelect();
},
});
}
}
import $ from 'jquery';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import CreateItemDropdown from '../create_item_dropdown';
import AccessorUtilities from '../lib/utils/accessor';
import AccessDropdown from '~/projects/settings/access_dropdown';
import axios from '~/lib/utils/axios_utils';
import AccessorUtilities from '~/lib/utils/accessor';
import Flash from '~/flash';
import CreateItemDropdown from '~/create_item_dropdown';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import { __ } from '~/locale';
export default class ProtectedBranchCreate {
constructor() {
constructor(options) {
this.hasLicense = options.hasLicense;
this.$form = $('.js-new-protected-branch');
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.currentProjectUserDefaults = {};
this.buildDropdowns();
this.$codeOwnerToggle = this.$form.find('.js-code-owner-toggle');
this.bindEvents();
}
bindEvents() {
if (this.hasLicense) {
this.$codeOwnerToggle.on('click', this.onCodeOwnerToggleClick.bind(this));
}
this.$form.on('submit', this.onFormSubmit.bind(this));
}
onCodeOwnerToggleClick() {
this.$codeOwnerToggle.toggleClass('is-checked');
}
buildDropdowns() {
const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push');
const $protectedBranchDropdown = this.$form.find('.js-protected-branch-select');
// Cache callback
this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Merge dropdown
this.protectedBranchMergeAccessDropdown = new ProtectedBranchAccessDropdown({
this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new AccessDropdown({
$dropdown: $allowedToMergeDropdown,
data: gon.merge_access_levels,
accessLevelsData: gon.merge_access_levels,
onSelect: this.onSelectCallback,
accessLevel: ACCESS_LEVELS.MERGE,
hasLicense: this.hasLicense,
});
// Allowed to Push dropdown
this.protectedBranchPushAccessDropdown = new ProtectedBranchAccessDropdown({
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new AccessDropdown({
$dropdown: $allowedToPushDropdown,
data: gon.push_access_levels,
accessLevelsData: gon.push_access_levels,
onSelect: this.onSelectCallback,
accessLevel: ACCESS_LEVELS.PUSH,
hasLicense: this.hasLicense,
});
this.createItemDropdown = new CreateItemDropdown({
$dropdown: $protectedBranchDropdown,
$dropdown: this.$form.find('.js-protected-branch-select'),
defaultToggleLabel: __('Protected Branch'),
fieldName: 'protected_branch[name]',
onSelect: this.onSelectCallback,
......@@ -43,26 +64,66 @@ export default class ProtectedBranchCreate {
});
}
// This will run after clicked callback
// Enable submit button after selecting an option
onSelect() {
// Enable submit button
const $branchInput = this.$form.find('input[name="protected_branch[name]"]');
const $allowedToMergeInput = this.$form.find(
'input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]',
);
const $allowedToPushInput = this.$form.find(
'input[name="protected_branch[push_access_levels_attributes][0][access_level]"]',
);
const completedForm = !(
$branchInput.val() &&
$allowedToMergeInput.length &&
$allowedToPushInput.length
const $allowedToMerge = this[`${ACCESS_LEVELS.MERGE}_dropdown`].getSelectedItems();
const $allowedToPush = this[`${ACCESS_LEVELS.PUSH}_dropdown`].getSelectedItems();
const toggle = !(
this.$form.find('input[name="protected_branch[name]"]').val() &&
$allowedToMerge.length &&
$allowedToPush.length
);
this.$form.find('input[type="submit"]').prop('disabled', completedForm);
this.$form.find('input[type="submit"]').attr('disabled', toggle);
}
static getProtectedBranches(term, callback) {
callback(gon.open_branches);
}
getFormData() {
const formData = {
authenticity_token: this.$form.find('input[name="authenticity_token"]').val(),
protected_branch: {
name: this.$form.find('input[name="protected_branch[name]"]').val(),
code_owner_approval_required: this.$codeOwnerToggle.hasClass('is-checked'),
},
};
Object.keys(ACCESS_LEVELS).forEach(level => {
const accessLevel = ACCESS_LEVELS[level];
const selectedItems = this[`${accessLevel}_dropdown`].getSelectedItems();
const levelAttributes = [];
selectedItems.forEach(item => {
if (item.type === LEVEL_TYPES.USER) {
levelAttributes.push({
user_id: item.user_id,
});
} else if (item.type === LEVEL_TYPES.ROLE) {
levelAttributes.push({
access_level: item.access_level,
});
} else if (item.type === LEVEL_TYPES.GROUP) {
levelAttributes.push({
group_id: item.group_id,
});
}
});
formData.protected_branch[`${accessLevel}_attributes`] = levelAttributes;
});
return formData;
}
onFormSubmit(e) {
e.preventDefault();
axios[this.$form.attr('method')](this.$form.attr('action'), this.getFormData())
.then(() => {
window.location.reload();
})
.catch(() => Flash(__('Failed to protect the branch')));
}
}
import flash from '../flash';
import axios from '../lib/utils/axios_utils';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown';
import { find } from 'lodash';
import AccessDropdown from '~/projects/settings/access_dropdown';
import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import { __ } from '~/locale';
export default class ProtectedBranchEdit {
constructor(options) {
this.hasLicense = options.hasLicense;
this.$wraps = {};
this.hasChanges = false;
this.$wrap = options.$wrap;
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
this.onSelectCallback = this.onSelect.bind(this);
this.$codeOwnerToggle = this.$wrap.find('.js-code-owner-toggle');
this.$wraps[ACCESS_LEVELS.MERGE] = this.$allowedToMergeDropdown.closest(
`.${ACCESS_LEVELS.MERGE}-container`,
);
this.$wraps[ACCESS_LEVELS.PUSH] = this.$allowedToPushDropdown.closest(
`.${ACCESS_LEVELS.PUSH}-container`,
);
this.buildDropdowns();
this.bindEvents();
}
bindEvents() {
if (this.hasLicense) {
this.$codeOwnerToggle.on('click', this.onCodeOwnerToggleClick.bind(this));
}
}
onCodeOwnerToggleClick() {
this.$codeOwnerToggle.toggleClass('is-checked');
this.$codeOwnerToggle.prop('disabled', true);
const formData = {
code_owner_approval_required: this.$codeOwnerToggle.hasClass('is-checked'),
};
this.updateCodeOwnerApproval(formData);
}
updateCodeOwnerApproval(formData) {
axios
.patch(this.$wrap.data('url'), {
protected_branch: formData,
})
.then(() => {
this.$codeOwnerToggle.prop('disabled', false);
})
.catch(() => {
Flash(__('Failed to update branch!'));
});
}
buildDropdowns() {
// Allowed to merge dropdown
this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new AccessDropdown({
accessLevel: ACCESS_LEVELS.MERGE,
accessLevelsData: gon.merge_access_levels,
$dropdown: this.$allowedToMergeDropdown,
data: gon.merge_access_levels,
onSelect: this.onSelectCallback,
onSelect: this.onSelectOption.bind(this),
onHide: this.onDropdownHide.bind(this),
hasLicense: this.hasLicense,
});
// Allowed to push dropdown
this.protectedBranchAccessDropdown = new ProtectedBranchAccessDropdown({
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new AccessDropdown({
accessLevel: ACCESS_LEVELS.PUSH,
accessLevelsData: gon.push_access_levels,
$dropdown: this.$allowedToPushDropdown,
data: gon.push_access_levels,
onSelect: this.onSelectCallback,
onSelect: this.onSelectOption.bind(this),
onHide: this.onDropdownHide.bind(this),
hasLicense: this.hasLicense,
});
}
onSelect() {
const $allowedToMergeInput = this.$wrap.find(
`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`,
);
const $allowedToPushInput = this.$wrap.find(
`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`,
);
onSelectOption() {
this.hasChanges = true;
}
// Do not update if one dropdown has not selected any option
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return;
onDropdownHide() {
if (!this.hasChanges) {
return;
}
this.$allowedToMergeDropdown.disable();
this.$allowedToPushDropdown.disable();
this.hasChanges = true;
this.updatePermissions();
}
updatePermissions() {
const formData = Object.keys(ACCESS_LEVELS).reduce((acc, level) => {
const accessLevelName = ACCESS_LEVELS[level];
const inputData = this[`${accessLevelName}_dropdown`].getInputData(accessLevelName);
acc[`${accessLevelName}_attributes`] = inputData;
return acc;
}, {});
axios
.patch(this.$wrap.data('url'), {
protected_branch: {
merge_access_levels_attributes: [
{
id: this.$allowedToMergeDropdown.data('accessLevelId'),
access_level: $allowedToMergeInput.val(),
},
],
push_access_levels_attributes: [
{
id: this.$allowedToPushDropdown.data('accessLevelId'),
access_level: $allowedToPushInput.val(),
},
],
},
protected_branch: formData,
})
.then(() => {
.then(({ data }) => {
this.hasChanges = false;
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(data[accessLevelName], `${accessLevelName}_dropdown`);
});
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
})
.catch(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
flash(
__('Failed to update branch!'),
'alert',
document.querySelector('.js-protected-branches-list'),
);
Flash(__('Failed to update branch!'));
});
}
setSelectedItemsToDropdown(items = [], dropdownName) {
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 = find(selectedItems, {
user_id: currentItem.user_id,
});
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,
};
} else if (currentItem.group_id) {
return {
id: currentItem.id,
group_id: currentItem.group_id,
type: LEVEL_TYPES.GROUP,
persisted: true,
};
}
return {
id: currentItem.id,
access_level: currentItem.access_level,
type: LEVEL_TYPES.ROLE,
persisted: true,
};
});
this[dropdownName].setSelectedItems(itemsToAdd);
}
}
......@@ -13,6 +13,7 @@ export default class ProtectedBranchEditList {
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new ProtectedBranchEdit({
$wrap: $(el),
hasLicense: false,
});
});
}
......
......@@ -62,7 +62,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
end
def access_level_attributes
%i[access_level id]
%i[access_level id _destroy]
end
end
......
......@@ -8,6 +8,14 @@ module BranchesHelper
def protected_branch?(project, branch)
ProtectedBranch.protected?(project, branch.name)
end
def access_levels_data(access_levels)
return [] unless access_levels
access_levels.map do |level|
{ id: level.id, type: :role, access_level: level.access_level }
end
end
end
BranchesHelper.prepend_if_ee('EE::BranchesHelper')
%td
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_levels.first.access_level
= dropdown_tag( (protected_branch.merge_access_levels.first.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header',
data: { field_name: "allowed_to_merge_#{protected_branch.id}", access_level_id: protected_branch.merge_access_levels.first.id }})
%td
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_levels.first.access_level
= dropdown_tag( (protected_branch.push_access_levels.first.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: protected_branch.push_access_levels.first.id }})
= render 'shared/projects/protected_branches/update_protected_branch', protected_branch: protected_branch
- merge_access_levels = protected_branch.merge_access_levels.for_role
- push_access_levels = protected_branch.push_access_levels.for_role
- user_merge_access_levels = protected_branch.merge_access_levels.for_user
- user_push_access_levels = protected_branch.push_access_levels.for_user
- group_merge_access_levels = protected_branch.merge_access_levels.for_group
- group_push_access_levels = protected_branch.push_access_levels.for_group
%td.merge_access_levels-container
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", merge_access_levels.first&.access_level
= dropdown_tag( (merge_access_levels.first&.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header',
data: { field_name: "allowed_to_merge_#{protected_branch.id}", preselected_items: access_levels_data(merge_access_levels) }})
- if user_merge_access_levels.any?
%p.small
= _('The following %{user} can also merge into this branch: %{branch}') % { user: 'user'.pluralize(user_merge_access_levels.size), branch: user_merge_access_levels.map(&:humanize).to_sentence }
- if group_merge_access_levels.any?
%p.small
= _('Members of %{group} can also merge into this branch: %{branch}') % { group: (group_merge_access_levels.size > 1 ? 'these groups' : 'this group'), branch: group_merge_access_levels.map(&:humanize).to_sentence }
%td.push_access_levels-container
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", push_access_levels.first&.access_level
= dropdown_tag( (push_access_levels.first&.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
data: { field_name: "allowed_to_push_#{protected_branch.id}", preselected_items: access_levels_data(push_access_levels) }})
- if user_push_access_levels.any?
%p.small
= _('The following %{user} can also push to this branch: %{branch}') % { user: 'user'.pluralize(user_push_access_levels.size), branch: user_push_access_levels.map(&:humanize).to_sentence }
- if group_push_access_levels.any?
%p.small
= _('Members of %{group} can also push to this branch: %{branch}') % { group: (group_push_access_levels.size > 1 ? 'these groups' : 'this group'), branch: group_push_access_levels.map(&:humanize).to_sentence }
/* eslint-disable no-new */
import ProtectedBranchCreate from 'ee/protected_branches/protected_branch_create';
import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
import ProtectedBranchEditList from 'ee/protected_branches/protected_branch_edit_list';
import ProtectedTagCreate from 'ee/protected_tags/protected_tag_create';
import ProtectedTagEditList from 'ee/protected_tags/protected_tag_edit_list';
......@@ -8,7 +8,6 @@ import UsersSelect from '~/users_select';
import UserCallout from '~/user_callout';
import initSettingsPanels from '~/settings_panels';
import initDeployKeys from '~/deploy_keys';
import CEProtectedBranchCreate from '~/protected_branches/protected_branch_create';
import CEProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
import CEProtectedTagCreate from '~/protected_tags/protected_tag_create';
import CEProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
......@@ -24,13 +23,13 @@ document.addEventListener('DOMContentLoaded', () => {
initSettingsPanels();
if (document.querySelector('.js-protected-refs-for-users')) {
new ProtectedBranchCreate();
new ProtectedBranchCreate({ hasLicense: true });
new ProtectedBranchEditList();
new ProtectedTagCreate();
new ProtectedTagEditList();
} else {
new CEProtectedBranchCreate();
new ProtectedBranchCreate({ hasLicense: false });
new CEProtectedBranchEditList();
new CEProtectedTagCreate();
......
/* eslint-disable no-unused-vars */
import $ from 'jquery';
import ProtectedBranchCreate from './protected_branch_create';
import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
import ProtectedBranchEditList from './protected_branch_edit_list';
$(() => {
const protectedBranchCreate = new ProtectedBranchCreate();
const protectedBranchCreate = new ProtectedBranchCreate({ hasLicense: true });
const protectedBranchEditList = new ProtectedBranchEditList();
});
import $ from 'jquery';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import axios from '~/lib/utils/axios_utils';
import AccessorUtilities from '~/lib/utils/accessor';
import Flash from '~/flash';
import CreateItemDropdown from '~/create_item_dropdown';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import { __ } from '~/locale';
export default class ProtectedBranchCreate {
constructor() {
this.$form = $('.js-new-protected-branch');
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.currentProjectUserDefaults = {};
this.buildDropdowns();
this.$branchInput = this.$form.find('input[name="protected_branch[name]"]');
this.$codeOwnerToggle = this.$form.find('.js-code-owner-toggle');
this.bindEvents();
}
bindEvents() {
this.$codeOwnerToggle.on('click', this.onCodeOwnerToggleClick.bind(this));
this.$form.on('submit', this.onFormSubmit.bind(this));
}
onCodeOwnerToggleClick() {
this.$codeOwnerToggle.toggleClass('is-checked');
}
buildDropdowns() {
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 AccessDropdown({
$dropdown: $allowedToMergeDropdown,
accessLevelsData: gon.merge_access_levels,
onSelect: this.onSelectCallback,
accessLevel: ACCESS_LEVELS.MERGE,
});
// Allowed to Push dropdown
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new AccessDropdown({
$dropdown: $allowedToPushDropdown,
accessLevelsData: gon.push_access_levels,
onSelect: this.onSelectCallback,
accessLevel: ACCESS_LEVELS.PUSH,
});
this.createItemDropdown = new CreateItemDropdown({
$dropdown: this.$form.find('.js-protected-branch-select'),
defaultToggleLabel: __('Protected Branch'),
fieldName: 'protected_branch[name]',
onSelect: this.onSelectCallback,
getData: ProtectedBranchCreate.getProtectedBranches,
});
}
// Enable submit button after selecting an option
onSelect() {
const $allowedToMerge = this[`${ACCESS_LEVELS.MERGE}_dropdown`].getSelectedItems();
const $allowedToPush = this[`${ACCESS_LEVELS.PUSH}_dropdown`].getSelectedItems();
const toggle = !(
this.$form.find('input[name="protected_branch[name]"]').val() &&
$allowedToMerge.length &&
$allowedToPush.length
);
this.$form.find('input[type="submit"]').attr('disabled', toggle);
}
static getProtectedBranches(term, callback) {
callback(gon.open_branches);
}
getFormData() {
const formData = {
authenticity_token: this.$form.find('input[name="authenticity_token"]').val(),
protected_branch: {
name: this.$form.find('input[name="protected_branch[name]"]').val(),
code_owner_approval_required: this.$codeOwnerToggle.hasClass('is-checked'),
},
};
Object.keys(ACCESS_LEVELS).forEach(level => {
const accessLevel = ACCESS_LEVELS[level];
const selectedItems = this[`${accessLevel}_dropdown`].getSelectedItems();
const levelAttributes = [];
selectedItems.forEach(item => {
if (item.type === LEVEL_TYPES.USER) {
levelAttributes.push({
user_id: item.user_id,
});
} else if (item.type === LEVEL_TYPES.ROLE) {
levelAttributes.push({
access_level: item.access_level,
});
} else if (item.type === LEVEL_TYPES.GROUP) {
levelAttributes.push({
group_id: item.group_id,
});
}
});
formData.protected_branch[`${accessLevel}_attributes`] = levelAttributes;
});
return formData;
}
onFormSubmit(e) {
e.preventDefault();
axios[this.$form.attr('method')](this.$form.attr('action'), this.getFormData())
.then(() => {
window.location.reload();
})
.catch(() => Flash(__('Failed to protect the branch')));
}
}
import { find } from 'lodash';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import { __ } from '~/locale';
export default class ProtectedBranchEdit {
constructor(options) {
this.$wraps = {};
this.hasChanges = false;
this.$wrap = options.$wrap;
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push');
this.$codeOwnerToggle = this.$wrap.find('.js-code-owner-toggle');
this.$wraps[ACCESS_LEVELS.MERGE] = this.$allowedToMergeDropdown.closest(
`.${ACCESS_LEVELS.MERGE}-container`,
);
this.$wraps[ACCESS_LEVELS.PUSH] = this.$allowedToPushDropdown.closest(
`.${ACCESS_LEVELS.PUSH}-container`,
);
this.buildDropdowns();
this.bindEvents();
}
bindEvents() {
this.$codeOwnerToggle.on('click', this.onCodeOwnerToggleClick.bind(this));
}
onCodeOwnerToggleClick() {
this.$codeOwnerToggle.toggleClass('is-checked');
this.$codeOwnerToggle.prop('disabled', true);
const formData = {
code_owner_approval_required: this.$codeOwnerToggle.hasClass('is-checked'),
};
this.updateCodeOwnerApproval(formData);
}
updateCodeOwnerApproval(formData) {
axios
.patch(this.$wrap.data('url'), {
protected_branch: formData,
})
.then(() => {
this.$codeOwnerToggle.prop('disabled', false);
})
.catch(() => {
Flash(__('Failed to update branch!'));
});
}
buildDropdowns() {
// Allowed to merge dropdown
this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new AccessDropdown({
accessLevel: ACCESS_LEVELS.MERGE,
accessLevelsData: gon.merge_access_levels,
$dropdown: this.$allowedToMergeDropdown,
onSelect: this.onSelectOption.bind(this),
onHide: this.onDropdownHide.bind(this),
});
// Allowed to push dropdown
this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new AccessDropdown({
accessLevel: ACCESS_LEVELS.PUSH,
accessLevelsData: gon.push_access_levels,
$dropdown: this.$allowedToPushDropdown,
onSelect: this.onSelectOption.bind(this),
onHide: this.onDropdownHide.bind(this),
});
}
onSelectOption() {
this.hasChanges = true;
}
onDropdownHide() {
if (!this.hasChanges) {
return;
}
this.hasChanges = true;
this.updatePermissions();
}
updatePermissions() {
const formData = Object.keys(ACCESS_LEVELS).reduce((acc, level) => {
const accessLevelName = ACCESS_LEVELS[level];
const inputData = this[`${accessLevelName}_dropdown`].getInputData(accessLevelName);
acc[`${accessLevelName}_attributes`] = inputData;
return acc;
}, {});
axios
.patch(this.$wrap.data('url'), {
protected_branch: formData,
})
.then(({ data }) => {
this.hasChanges = false;
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(data[accessLevelName], `${accessLevelName}_dropdown`);
});
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
})
.catch(() => {
this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable();
Flash(__('Failed to update branch!'));
});
}
setSelectedItemsToDropdown(items = [], dropdownName) {
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 = find(selectedItems, {
user_id: currentItem.user_id,
});
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,
};
} else if (currentItem.group_id) {
return {
id: currentItem.id,
group_id: currentItem.group_id,
type: LEVEL_TYPES.GROUP,
persisted: true,
};
}
return {
id: currentItem.id,
access_level: currentItem.access_level,
type: LEVEL_TYPES.ROLE,
persisted: true,
};
});
this[dropdownName].setSelectedItems(itemsToAdd);
}
}
/* eslint-disable no-new */
import $ from 'jquery';
import ProtectedBranchEdit from './protected_branch_edit';
import ProtectedBranchEdit from '~/protected_branches/protected_branch_edit';
export default class ProtectedBranchEditList {
constructor() {
......@@ -14,6 +14,7 @@ export default class ProtectedBranchEditList {
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new ProtectedBranchEdit({
$wrap: $(el),
hasLicense: true,
});
});
}
......
import $ from 'jquery';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import AccessDropdown from '~/projects/settings/access_dropdown';
import axios from '~/lib/utils/axios_utils';
import AccessorUtilities from '~/lib/utils/accessor';
import Flash from '~/flash';
......
import $ from 'jquery';
import { find } from 'lodash';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import AccessDropdown from '~/projects/settings/access_dropdown';
import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
......
import $ from 'jquery';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import AccessDropdown from '~/projects/settings/access_dropdown';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import CreateItemDropdown from '~/create_item_dropdown';
......
import $ from 'jquery';
import { find } from 'lodash';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import AccessDropdown from '~/projects/settings/access_dropdown';
import axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash';
import { s__ } from '~/locale';
......
......@@ -9,7 +9,7 @@ module EE
override :access_level_attributes
def access_level_attributes
super + %i[user_id _destroy group_id]
super + %i[user_id group_id]
end
end
end
......
......@@ -2,6 +2,8 @@
module EE
module BranchesHelper
extend ::Gitlab::Utils::Override
# Returns a hash were keys are types of access levels (user, role), and
# values are the number of access levels of the particular type.
def access_level_frequencies(access_levels)
......@@ -10,7 +12,10 @@ module EE
end
end
override :access_levels_data
def access_levels_data(access_levels)
return [] unless access_levels
access_levels.map do |level|
if level.type == :user
{
......
- merge_access_level = protected_branch.merge_access_levels.for_role.first
- push_access_level = protected_branch.push_access_levels.for_role.first
= render 'shared/projects/protected_branches/update_protected_branch', protected_branch: protected_branch
- user_merge_access_levels = protected_branch.merge_access_levels.for_user
- user_push_access_levels = protected_branch.push_access_levels.for_user
- group_merge_access_levels = protected_branch.merge_access_levels.for_group
- group_push_access_levels = protected_branch.push_access_levels.for_group
%td
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", merge_access_level&.access_level
= dropdown_tag( (merge_access_level&.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header',
data: { field_name: "allowed_to_merge_#{protected_branch.id}", access_level_id: merge_access_level&.id }})
- if user_merge_access_levels.any?
%p.small
The following
#{ 'user'.pluralize(user_merge_access_levels.size) }
can also merge into this branch:
#{ user_merge_access_levels.map(&:humanize).to_sentence }
- if group_merge_access_levels.any?
%p.small
Members of
#{ group_merge_access_levels.size > 1 ? 'these groups' : 'this group' }
can also merge into this branch:
#{ group_merge_access_levels.map(&:humanize).to_sentence }
%td
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", push_access_level&.access_level
= dropdown_tag( (push_access_level&.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-push', dropdown_class: 'dropdown-menu-selectable js-allowed-to-push-container capitalize-header',
data: { field_name: "allowed_to_push_#{protected_branch.id}", access_level_id: push_access_level&.id }})
- if user_push_access_levels.any?
%p.small
The following
#{ 'user'.pluralize(user_push_access_levels.size) }
can also push to this branch:
#{ user_push_access_levels.map(&:humanize).to_sentence }
- if group_push_access_levels.any?
%p.small
Members of
#{ group_push_access_levels.size > 1 ? 'these groups' : 'this group' }
can also push to this branch:
#{ group_push_access_levels.map(&:humanize).to_sentence }
# frozen_string_literal: true
FactoryBot.define do
FactoryBot.modify do
factory :protected_branch_merge_access_level, class: 'ProtectedBranch::MergeAccessLevel' do
user { nil }
group { nil }
protected_branch
access_level { Gitlab::Access::DEVELOPER }
end
end
# frozen_string_literal: true
FactoryBot.define do
FactoryBot.modify do
factory :protected_branch_push_access_level, class: 'ProtectedBranch::PushAccessLevel' do
user { nil }
group { nil }
protected_branch
access_level { Gitlab::Access::DEVELOPER }
end
end
import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import ProtectedBranchEdit from 'ee/protected_branches/protected_branch_edit';
import ProtectedBranchEdit from '~/protected_branches/protected_branch_edit';
import { TEST_HOST } from 'helpers/test_constants';
import flash from '~/flash';
import axios from '~/lib/utils/axios_utils';
......@@ -30,7 +30,7 @@ describe('EE ProtectedBranchEdit', () => {
findCodeOwnerToggle().classList.add(IS_CHECKED_CLASS);
}
return new ProtectedBranchEdit({ $wrap: $('#wrap') });
return new ProtectedBranchEdit({ $wrap: $('#wrap'), hasLicense: true });
};
afterEach(() => {
......
......@@ -14867,6 +14867,12 @@ msgstr ""
msgid "Members invited to %{strong_start}%{group_name}%{strong_end}"
msgstr ""
msgid "Members of %{group} can also merge into this branch: %{branch}"
msgstr ""
msgid "Members of %{group} can also push to this branch: %{branch}"
msgstr ""
msgid "Members of %{strong_open}%{project_name}%{strong_close}"
msgstr ""
......@@ -24123,6 +24129,12 @@ msgstr ""
msgid "The file name should have a .yml extension"
msgstr ""
msgid "The following %{user} can also merge into this branch: %{branch}"
msgstr ""
msgid "The following %{user} can also push to this branch: %{branch}"
msgstr ""
msgid "The following items will NOT be exported:"
msgstr ""
......
......@@ -17,7 +17,7 @@ module QA
element :allowed_to_merge_dropdown
end
view 'app/views/projects/protected_branches/_update_protected_branch.html.haml' do
view 'app/views/shared/projects/protected_branches/_update_protected_branch.html.haml' do
element :allowed_to_merge
end
......
# frozen_string_literal: true
FactoryBot.define do
factory :protected_branch_merge_access_level, class: 'ProtectedBranch::MergeAccessLevel' do
protected_branch
access_level { Gitlab::Access::DEVELOPER }
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :protected_branch_push_access_level, class: 'ProtectedBranch::PushAccessLevel' do
protected_branch
access_level { Gitlab::Access::DEVELOPER }
end
end
import $ from 'jquery';
import '~/gl_dropdown';
import AccessDropdown from 'ee/projects/settings/access_dropdown';
import { LEVEL_TYPES } from 'ee/projects/settings/constants';
import AccessDropdown from '~/projects/settings/access_dropdown';
import { LEVEL_TYPES } from '~/projects/settings/constants';
describe('AccessDropdown', () => {
const defaultLabel = 'dummy default label';
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BranchesHelper do
describe '#access_levels_data' do
subject { helper.access_levels_data(access_levels) }
context 'when access_levels is nil' do
let(:access_levels) { nil }
it { is_expected.to be_empty }
end
context 'when access levels are provided' do
let(:protected_branch) { create(:protected_branch, :developers_can_merge, :maintainers_can_push) }
let(:merge_level) { protected_branch.merge_access_levels.first }
let(:push_level) { protected_branch.push_access_levels.first }
let(:access_levels) { [merge_level, push_level] }
it 'returns the correct array' do
expected_array = [
{ id: merge_level.id, type: :role, access_level: Gitlab::Access::DEVELOPER },
{ id: push_level.id, type: :role, access_level: Gitlab::Access::MAINTAINER }
]
expect(subject).to eq(expected_array)
end
end
end
end
......@@ -27,4 +27,9 @@ module ProtectedBranchHelpers
set_allowed_to('merge')
set_allowed_to('push')
end
def click_on_protect
click_on "Protect"
wait_for_requests
end
end
......@@ -22,7 +22,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end
end
click_on "Protect"
click_on_protect
expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id])
......@@ -45,7 +45,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click
end
click_on "Protect"
click_on_protect
expect(ProtectedBranch.count).to eq(1)
......@@ -85,7 +85,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click
end
click_on "Protect"
click_on_protect
expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id])
......@@ -108,7 +108,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click
end
click_on "Protect"
click_on_protect
expect(ProtectedBranch.count).to eq(1)
......
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