Commit 4f6d51a3 authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch '30769-foss-ee-protected-branches-try-again' into 'master'

RUN AS-IF-FOSS Consolidated FOSS EE ProtectedBranch JS files

See merge request gitlab-org/gitlab!38063
parents 29518bb0 957f7aef
...@@ -14,7 +14,7 @@ export default () => { ...@@ -14,7 +14,7 @@ export default () => {
new ProtectedTagEditList(); new ProtectedTagEditList();
initDeployKeys(); initDeployKeys();
initSettingsPanels(); initSettingsPanels();
new ProtectedBranchCreate(); new ProtectedBranchCreate({ hasLicense: false });
new ProtectedBranchEditList(); new ProtectedBranchEditList();
new DueDateSelectors(); new DueDateSelectors();
fileUpload('.js-choose-file', '.js-object-map-input'); fileUpload('.js-choose-file', '.js-object-map-input');
......
...@@ -7,8 +7,9 @@ import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants'; ...@@ -7,8 +7,9 @@ import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants';
export default class AccessDropdown { export default class AccessDropdown {
constructor(options) { constructor(options) {
const { $dropdown, accessLevel, accessLevelsData } = options; const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options;
this.options = options; this.options = options;
this.hasLicense = hasLicense;
this.groups = []; this.groups = [];
this.accessLevel = accessLevel; this.accessLevel = accessLevel;
this.accessLevelsData = accessLevelsData.roles; this.accessLevelsData = accessLevelsData.roles;
...@@ -47,9 +48,18 @@ export default class AccessDropdown { ...@@ -47,9 +48,18 @@ export default class AccessDropdown {
e.preventDefault(); 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 ($el.is('.is-active')) {
if (this.noOneObj) { if (this.noOneObj) {
if (item.id === this.noOneObj.id) { if (item.id === this.noOneObj.id && this.hasLicense) {
// remove all others selected items // remove all others selected items
this.accessLevelsData.forEach(level => { this.accessLevelsData.forEach(level => {
if (level.id !== item.id) { if (level.id !== item.id) {
...@@ -286,6 +296,7 @@ export default class AccessDropdown { ...@@ -286,6 +296,7 @@ export default class AccessDropdown {
} }
getData(query, callback) { getData(query, callback) {
if (this.hasLicense) {
Promise.all([ Promise.all([
this.getUsers(query), this.getUsers(query),
this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(), this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(),
...@@ -295,12 +306,13 @@ export default class AccessDropdown { ...@@ -295,12 +306,13 @@ export default class AccessDropdown {
callback(this.consolidateData(usersResponse.data, groupsResponse.data)); callback(this.consolidateData(usersResponse.data, groupsResponse.data));
}) })
.catch(() => Flash(__('Failed to load groups & users.'))); .catch(() => Flash(__('Failed to load groups & users.')));
} else {
callback(this.consolidateData());
}
} }
consolidateData(usersResponse, groupsResponse) { consolidateData(usersResponse = [], groupsResponse = []) {
let consolidatedData = []; let consolidatedData = [];
const map = [];
const selectedItems = this.getSelectedItems();
// ID property is handled differently locally from the server // ID property is handled differently locally from the server
// //
...@@ -316,14 +328,6 @@ export default class AccessDropdown { ...@@ -316,14 +328,6 @@ export default class AccessDropdown {
// In dropdown: `id` // In dropdown: `id`
// For submit: `user_id` // For submit: `user_id`
/*
* Build groups
*/
const groups = groupsResponse.map(group => ({
...group,
type: LEVEL_TYPES.GROUP,
}));
/* /*
* Build roles * Build roles
*/ */
...@@ -338,6 +342,24 @@ export default class AccessDropdown { ...@@ -338,6 +342,24 @@ export default class AccessDropdown {
return level; return level;
}); });
if (roles.length) {
consolidatedData = consolidatedData.concat(
[{ type: 'header', content: s__('AccessDropdown|Roles') }],
roles,
);
}
if (this.hasLicense) {
const map = [];
const selectedItems = this.getSelectedItems();
/*
* Build groups
*/
const groups = groupsResponse.map(group => ({
...group,
type: LEVEL_TYPES.GROUP,
}));
/* /*
* Build users * Build users
*/ */
...@@ -367,13 +389,6 @@ export default class AccessDropdown { ...@@ -367,13 +389,6 @@ export default class AccessDropdown {
} }
}); });
if (roles.length) {
consolidatedData = consolidatedData.concat(
[{ type: 'header', content: s__('AccessDropdown|Roles') }],
roles,
);
}
if (groups.length) { if (groups.length) {
if (roles.length) { if (roles.length) {
consolidatedData = consolidatedData.concat([{ type: 'divider' }]); consolidatedData = consolidatedData.concat([{ type: 'divider' }]);
...@@ -392,6 +407,7 @@ export default class AccessDropdown { ...@@ -392,6 +407,7 @@ export default class AccessDropdown {
users, users,
); );
} }
}
return consolidatedData; 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 $ from 'jquery';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; import AccessDropdown from '~/projects/settings/access_dropdown';
import CreateItemDropdown from '../create_item_dropdown'; import axios from '~/lib/utils/axios_utils';
import AccessorUtilities from '../lib/utils/accessor'; 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'; import { __ } from '~/locale';
export default class ProtectedBranchCreate { export default class ProtectedBranchCreate {
constructor() { constructor(options) {
this.hasLicense = options.hasLicense;
this.$form = $('.js-new-protected-branch'); this.$form = $('.js-new-protected-branch');
this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe();
this.currentProjectUserDefaults = {}; this.currentProjectUserDefaults = {};
this.buildDropdowns(); 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() { buildDropdowns() {
const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge'); const $allowedToMergeDropdown = this.$form.find('.js-allowed-to-merge');
const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push'); const $allowedToPushDropdown = this.$form.find('.js-allowed-to-push');
const $protectedBranchDropdown = this.$form.find('.js-protected-branch-select');
// Cache callback // Cache callback
this.onSelectCallback = this.onSelect.bind(this); this.onSelectCallback = this.onSelect.bind(this);
// Allowed to Merge dropdown // Allowed to Merge dropdown
this.protectedBranchMergeAccessDropdown = new ProtectedBranchAccessDropdown({ this[`${ACCESS_LEVELS.MERGE}_dropdown`] = new AccessDropdown({
$dropdown: $allowedToMergeDropdown, $dropdown: $allowedToMergeDropdown,
data: gon.merge_access_levels, accessLevelsData: gon.merge_access_levels,
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
accessLevel: ACCESS_LEVELS.MERGE,
hasLicense: this.hasLicense,
}); });
// Allowed to Push dropdown // Allowed to Push dropdown
this.protectedBranchPushAccessDropdown = new ProtectedBranchAccessDropdown({ this[`${ACCESS_LEVELS.PUSH}_dropdown`] = new AccessDropdown({
$dropdown: $allowedToPushDropdown, $dropdown: $allowedToPushDropdown,
data: gon.push_access_levels, accessLevelsData: gon.push_access_levels,
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
accessLevel: ACCESS_LEVELS.PUSH,
hasLicense: this.hasLicense,
}); });
this.createItemDropdown = new CreateItemDropdown({ this.createItemDropdown = new CreateItemDropdown({
$dropdown: $protectedBranchDropdown, $dropdown: this.$form.find('.js-protected-branch-select'),
defaultToggleLabel: __('Protected Branch'), defaultToggleLabel: __('Protected Branch'),
fieldName: 'protected_branch[name]', fieldName: 'protected_branch[name]',
onSelect: this.onSelectCallback, onSelect: this.onSelectCallback,
...@@ -43,26 +64,66 @@ export default class ProtectedBranchCreate { ...@@ -43,26 +64,66 @@ export default class ProtectedBranchCreate {
}); });
} }
// This will run after clicked callback // Enable submit button after selecting an option
onSelect() { onSelect() {
// Enable submit button const $allowedToMerge = this[`${ACCESS_LEVELS.MERGE}_dropdown`].getSelectedItems();
const $branchInput = this.$form.find('input[name="protected_branch[name]"]'); const $allowedToPush = this[`${ACCESS_LEVELS.PUSH}_dropdown`].getSelectedItems();
const $allowedToMergeInput = this.$form.find( const toggle = !(
'input[name="protected_branch[merge_access_levels_attributes][0][access_level]"]', this.$form.find('input[name="protected_branch[name]"]').val() &&
); $allowedToMerge.length &&
const $allowedToPushInput = this.$form.find( $allowedToPush.length
'input[name="protected_branch[push_access_levels_attributes][0][access_level]"]',
);
const completedForm = !(
$branchInput.val() &&
$allowedToMergeInput.length &&
$allowedToPushInput.length
); );
this.$form.find('input[type="submit"]').prop('disabled', completedForm); this.$form.find('input[type="submit"]').attr('disabled', toggle);
} }
static getProtectedBranches(term, callback) { static getProtectedBranches(term, callback) {
callback(gon.open_branches); 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 { find } from 'lodash';
import axios from '../lib/utils/axios_utils'; import AccessDropdown from '~/projects/settings/access_dropdown';
import ProtectedBranchAccessDropdown from './protected_branch_access_dropdown'; import axios from '~/lib/utils/axios_utils';
import Flash from '~/flash';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
import { __ } from '~/locale'; import { __ } from '~/locale';
export default class ProtectedBranchEdit { export default class ProtectedBranchEdit {
constructor(options) { constructor(options) {
this.hasLicense = options.hasLicense;
this.$wraps = {};
this.hasChanges = false;
this.$wrap = options.$wrap; this.$wrap = options.$wrap;
this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge'); this.$allowedToMergeDropdown = this.$wrap.find('.js-allowed-to-merge');
this.$allowedToPushDropdown = this.$wrap.find('.js-allowed-to-push'); 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.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() { buildDropdowns() {
// Allowed to merge dropdown // 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, $dropdown: this.$allowedToMergeDropdown,
data: gon.merge_access_levels, onSelect: this.onSelectOption.bind(this),
onSelect: this.onSelectCallback, onHide: this.onDropdownHide.bind(this),
hasLicense: this.hasLicense,
}); });
// Allowed to push dropdown // 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, $dropdown: this.$allowedToPushDropdown,
data: gon.push_access_levels, onSelect: this.onSelectOption.bind(this),
onSelect: this.onSelectCallback, onHide: this.onDropdownHide.bind(this),
hasLicense: this.hasLicense,
}); });
} }
onSelect() { onSelectOption() {
const $allowedToMergeInput = this.$wrap.find( this.hasChanges = true;
`input[name="${this.$allowedToMergeDropdown.data('fieldName')}"]`, }
);
const $allowedToPushInput = this.$wrap.find( onDropdownHide() {
`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`, if (!this.hasChanges) {
); return;
}
// Do not update if one dropdown has not selected any option this.hasChanges = true;
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return; 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;
this.$allowedToMergeDropdown.disable(); return acc;
this.$allowedToPushDropdown.disable(); }, {});
axios axios
.patch(this.$wrap.data('url'), { .patch(this.$wrap.data('url'), {
protected_branch: { protected_branch: formData,
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(),
},
],
},
}) })
.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.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable(); this.$allowedToPushDropdown.enable();
}) })
.catch(() => { .catch(() => {
this.$allowedToMergeDropdown.enable(); this.$allowedToMergeDropdown.enable();
this.$allowedToPushDropdown.enable(); this.$allowedToPushDropdown.enable();
Flash(__('Failed to update branch!'));
});
}
flash( setSelectedItemsToDropdown(items = [], dropdownName) {
__('Failed to update branch!'), const itemsToAdd = items.map(currentItem => {
'alert', if (currentItem.user_id) {
document.querySelector('.js-protected-branches-list'), // 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 { ...@@ -13,6 +13,7 @@ export default class ProtectedBranchEditList {
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => { this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new ProtectedBranchEdit({ new ProtectedBranchEdit({
$wrap: $(el), $wrap: $(el),
hasLicense: false,
}); });
}); });
} }
......
...@@ -62,7 +62,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController ...@@ -62,7 +62,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
end end
def access_level_attributes def access_level_attributes
%i[access_level id] %i[access_level id _destroy]
end end
end end
......
...@@ -8,6 +8,14 @@ module BranchesHelper ...@@ -8,6 +8,14 @@ module BranchesHelper
def protected_branch?(project, branch) def protected_branch?(project, branch)
ProtectedBranch.protected?(project, branch.name) ProtectedBranch.protected?(project, branch.name)
end 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 end
BranchesHelper.prepend_if_ee('EE::BranchesHelper') BranchesHelper.prepend_if_ee('EE::BranchesHelper')
%td = render 'shared/projects/protected_branches/update_protected_branch', protected_branch: protected_branch
= 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 }})
- 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 */ /* 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 ProtectedBranchEditList from 'ee/protected_branches/protected_branch_edit_list';
import ProtectedTagCreate from 'ee/protected_tags/protected_tag_create'; import ProtectedTagCreate from 'ee/protected_tags/protected_tag_create';
import ProtectedTagEditList from 'ee/protected_tags/protected_tag_edit_list'; import ProtectedTagEditList from 'ee/protected_tags/protected_tag_edit_list';
...@@ -8,7 +8,6 @@ import UsersSelect from '~/users_select'; ...@@ -8,7 +8,6 @@ import UsersSelect from '~/users_select';
import UserCallout from '~/user_callout'; import UserCallout from '~/user_callout';
import initSettingsPanels from '~/settings_panels'; import initSettingsPanels from '~/settings_panels';
import initDeployKeys from '~/deploy_keys'; import initDeployKeys from '~/deploy_keys';
import CEProtectedBranchCreate from '~/protected_branches/protected_branch_create';
import CEProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list'; import CEProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
import CEProtectedTagCreate from '~/protected_tags/protected_tag_create'; import CEProtectedTagCreate from '~/protected_tags/protected_tag_create';
import CEProtectedTagEditList from '~/protected_tags/protected_tag_edit_list'; import CEProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
...@@ -24,13 +23,13 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -24,13 +23,13 @@ document.addEventListener('DOMContentLoaded', () => {
initSettingsPanels(); initSettingsPanels();
if (document.querySelector('.js-protected-refs-for-users')) { if (document.querySelector('.js-protected-refs-for-users')) {
new ProtectedBranchCreate(); new ProtectedBranchCreate({ hasLicense: true });
new ProtectedBranchEditList(); new ProtectedBranchEditList();
new ProtectedTagCreate(); new ProtectedTagCreate();
new ProtectedTagEditList(); new ProtectedTagEditList();
} else { } else {
new CEProtectedBranchCreate(); new ProtectedBranchCreate({ hasLicense: false });
new CEProtectedBranchEditList(); new CEProtectedBranchEditList();
new CEProtectedTagCreate(); new CEProtectedTagCreate();
......
/* eslint-disable no-unused-vars */ /* eslint-disable no-unused-vars */
import $ from 'jquery'; import $ from 'jquery';
import ProtectedBranchCreate from './protected_branch_create'; import ProtectedBranchCreate from '~/protected_branches/protected_branch_create';
import ProtectedBranchEditList from './protected_branch_edit_list'; import ProtectedBranchEditList from './protected_branch_edit_list';
$(() => { $(() => {
const protectedBranchCreate = new ProtectedBranchCreate(); const protectedBranchCreate = new ProtectedBranchCreate({ hasLicense: true });
const protectedBranchEditList = new ProtectedBranchEditList(); 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 */ /* eslint-disable no-new */
import $ from 'jquery'; import $ from 'jquery';
import ProtectedBranchEdit from './protected_branch_edit'; import ProtectedBranchEdit from '~/protected_branches/protected_branch_edit';
export default class ProtectedBranchEditList { export default class ProtectedBranchEditList {
constructor() { constructor() {
...@@ -14,6 +14,7 @@ export default class ProtectedBranchEditList { ...@@ -14,6 +14,7 @@ export default class ProtectedBranchEditList {
this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => { this.$wrap.find('.js-protected-branch-edit-form').each((i, el) => {
new ProtectedBranchEdit({ new ProtectedBranchEdit({
$wrap: $(el), $wrap: $(el),
hasLicense: true,
}); });
}); });
} }
......
import $ from 'jquery'; 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 axios from '~/lib/utils/axios_utils';
import AccessorUtilities from '~/lib/utils/accessor'; import AccessorUtilities from '~/lib/utils/accessor';
import Flash from '~/flash'; import Flash from '~/flash';
......
import $ from 'jquery'; import $ from 'jquery';
import { find } from 'lodash'; 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 axios from '~/lib/utils/axios_utils';
import Flash from '~/flash'; import Flash from '~/flash';
import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; import { ACCESS_LEVELS, LEVEL_TYPES } from './constants';
......
import $ from 'jquery'; 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 axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import CreateItemDropdown from '~/create_item_dropdown'; import CreateItemDropdown from '~/create_item_dropdown';
......
import $ from 'jquery'; import $ from 'jquery';
import { find } from 'lodash'; 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 axios from '~/lib/utils/axios_utils';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
......
...@@ -9,7 +9,7 @@ module EE ...@@ -9,7 +9,7 @@ module EE
override :access_level_attributes override :access_level_attributes
def access_level_attributes def access_level_attributes
super + %i[user_id _destroy group_id] super + %i[user_id group_id]
end end
end end
end end
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module EE module EE
module BranchesHelper module BranchesHelper
extend ::Gitlab::Utils::Override
# Returns a hash were keys are types of access levels (user, role), and # Returns a hash were keys are types of access levels (user, role), and
# values are the number of access levels of the particular type. # values are the number of access levels of the particular type.
def access_level_frequencies(access_levels) def access_level_frequencies(access_levels)
...@@ -10,7 +12,10 @@ module EE ...@@ -10,7 +12,10 @@ module EE
end end
end end
override :access_levels_data
def access_levels_data(access_levels) def access_levels_data(access_levels)
return [] unless access_levels
access_levels.map do |level| access_levels.map do |level|
if level.type == :user if level.type == :user
{ {
......
- merge_access_level = protected_branch.merge_access_levels.for_role.first = render 'shared/projects/protected_branches/update_protected_branch', protected_branch: protected_branch
- push_access_level = protected_branch.push_access_levels.for_role.first
- 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 # frozen_string_literal: true
FactoryBot.define do FactoryBot.modify do
factory :protected_branch_merge_access_level, class: 'ProtectedBranch::MergeAccessLevel' do factory :protected_branch_merge_access_level, class: 'ProtectedBranch::MergeAccessLevel' do
user { nil } user { nil }
group { nil } group { nil }
protected_branch
access_level { Gitlab::Access::DEVELOPER }
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
FactoryBot.define do FactoryBot.modify do
factory :protected_branch_push_access_level, class: 'ProtectedBranch::PushAccessLevel' do factory :protected_branch_push_access_level, class: 'ProtectedBranch::PushAccessLevel' do
user { nil } user { nil }
group { nil } group { nil }
protected_branch
access_level { Gitlab::Access::DEVELOPER }
end end
end end
import $ from 'jquery'; import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter'; 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 { TEST_HOST } from 'helpers/test_constants';
import flash from '~/flash'; import flash from '~/flash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
...@@ -30,7 +30,7 @@ describe('EE ProtectedBranchEdit', () => { ...@@ -30,7 +30,7 @@ describe('EE ProtectedBranchEdit', () => {
findCodeOwnerToggle().classList.add(IS_CHECKED_CLASS); findCodeOwnerToggle().classList.add(IS_CHECKED_CLASS);
} }
return new ProtectedBranchEdit({ $wrap: $('#wrap') }); return new ProtectedBranchEdit({ $wrap: $('#wrap'), hasLicense: true });
}; };
afterEach(() => { afterEach(() => {
......
...@@ -14858,6 +14858,12 @@ msgstr "" ...@@ -14858,6 +14858,12 @@ msgstr ""
msgid "Members invited to %{strong_start}%{group_name}%{strong_end}" msgid "Members invited to %{strong_start}%{group_name}%{strong_end}"
msgstr "" 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}" msgid "Members of %{strong_open}%{project_name}%{strong_close}"
msgstr "" msgstr ""
...@@ -24114,6 +24120,12 @@ msgstr "" ...@@ -24114,6 +24120,12 @@ msgstr ""
msgid "The file name should have a .yml extension" msgid "The file name should have a .yml extension"
msgstr "" 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:" msgid "The following items will NOT be exported:"
msgstr "" msgstr ""
......
...@@ -17,7 +17,7 @@ module QA ...@@ -17,7 +17,7 @@ module QA
element :allowed_to_merge_dropdown element :allowed_to_merge_dropdown
end 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 element :allowed_to_merge
end 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 $ from 'jquery';
import '~/gl_dropdown'; import '~/gl_dropdown';
import AccessDropdown from 'ee/projects/settings/access_dropdown'; import AccessDropdown from '~/projects/settings/access_dropdown';
import { LEVEL_TYPES } from 'ee/projects/settings/constants'; import { LEVEL_TYPES } from '~/projects/settings/constants';
describe('AccessDropdown', () => { describe('AccessDropdown', () => {
const defaultLabel = 'dummy default label'; 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 ...@@ -27,4 +27,9 @@ module ProtectedBranchHelpers
set_allowed_to('merge') set_allowed_to('merge')
set_allowed_to('push') set_allowed_to('push')
end end
def click_on_protect
click_on "Protect"
wait_for_requests
end
end end
...@@ -22,7 +22,7 @@ RSpec.shared_examples "protected branches > access control > CE" do ...@@ -22,7 +22,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
end end
end end
click_on "Protect" click_on_protect
expect(ProtectedBranch.count).to eq(1) expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to eq([access_type_id]) 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 ...@@ -45,7 +45,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click find(:link, 'No one').click
end end
click_on "Protect" click_on_protect
expect(ProtectedBranch.count).to eq(1) expect(ProtectedBranch.count).to eq(1)
...@@ -85,7 +85,7 @@ RSpec.shared_examples "protected branches > access control > CE" do ...@@ -85,7 +85,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click find(:link, 'No one').click
end end
click_on "Protect" click_on_protect
expect(ProtectedBranch.count).to eq(1) expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to eq([access_type_id]) 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 ...@@ -108,7 +108,7 @@ RSpec.shared_examples "protected branches > access control > CE" do
find(:link, 'No one').click find(:link, 'No one').click
end end
click_on "Protect" click_on_protect
expect(ProtectedBranch.count).to eq(1) 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