Commit ee1a8eef authored by Andrew Fontaine's avatar Andrew Fontaine

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

Consolidate FOSS and EE ProtectedBranch JS files

See merge request gitlab-org/gitlab!37499
parents bc689b40 878b344f
...@@ -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,21 +296,23 @@ export default class AccessDropdown { ...@@ -286,21 +296,23 @@ export default class AccessDropdown {
} }
getData(query, callback) { getData(query, callback) {
Promise.all([ if (this.hasLicense) {
this.getUsers(query), Promise.all([
this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(), this.getUsers(query),
]) this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(),
.then(([usersResponse, groupsResponse]) => { ])
this.groupsData = groupsResponse; .then(([usersResponse, groupsResponse]) => {
callback(this.consolidateData(usersResponse.data, groupsResponse.data)); this.groupsData = groupsResponse;
}) 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,35 +342,6 @@ export default class AccessDropdown { ...@@ -338,35 +342,6 @@ export default class AccessDropdown {
return level; 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) { if (roles.length) {
consolidatedData = consolidatedData.concat( consolidatedData = consolidatedData.concat(
[{ type: 'header', content: s__('AccessDropdown|Roles') }], [{ type: 'header', content: s__('AccessDropdown|Roles') }],
...@@ -374,23 +349,64 @@ export default class AccessDropdown { ...@@ -374,23 +349,64 @@ export default class AccessDropdown {
); );
} }
if (groups.length) { if (this.hasLicense) {
if (roles.length) { const map = [];
consolidatedData = consolidatedData.concat([{ type: 'divider' }]); 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( if (groups.length) {
[{ type: 'header', content: s__('AccessDropdown|Groups') }], if (roles.length) {
groups, consolidatedData = consolidatedData.concat([{ type: 'divider' }]);
); }
}
if (users.length) { consolidatedData = consolidatedData.concat(
consolidatedData = consolidatedData.concat( [{ type: 'header', content: s__('AccessDropdown|Groups') }],
[{ type: 'divider' }], groups,
[{ type: 'header', content: s__('AccessDropdown|Users') }], );
users, }
);
if (users.length) {
consolidatedData = consolidatedData.concat(
[{ type: 'divider' }],
[{ type: 'header', content: s__('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(
`input[name="${this.$allowedToPushDropdown.data('fieldName')}"]`,
);
// Do not update if one dropdown has not selected any option onDropdownHide() {
if (!($allowedToMergeInput.length && $allowedToPushInput.length)) return; if (!this.hasChanges) {
return;
}
this.$allowedToMergeDropdown.disable(); this.hasChanges = true;
this.$allowedToPushDropdown.disable(); 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 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(
__('Failed to update branch!'),
'alert',
document.querySelector('.js-protected-branches-list'),
);
}); });
} }
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 { ...@@ -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,
}); });
}); });
} }
......
/* 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';
......
- merge_access_level = protected_branch.merge_access_levels.for_role.first - merge_access_levels = protected_branch.merge_access_levels.for_role
- push_access_level = protected_branch.push_access_levels.for_role.first - push_access_levels = protected_branch.push_access_levels.for_role
- user_merge_access_levels = protected_branch.merge_access_levels.for_user - user_merge_access_levels = protected_branch.merge_access_levels.for_user
- user_push_access_levels = protected_branch.push_access_levels.for_user - user_push_access_levels = protected_branch.push_access_levels.for_user
...@@ -7,11 +7,11 @@ ...@@ -7,11 +7,11 @@
- group_merge_access_levels = protected_branch.merge_access_levels.for_group - group_merge_access_levels = protected_branch.merge_access_levels.for_group
- group_push_access_levels = protected_branch.push_access_levels.for_group - group_push_access_levels = protected_branch.push_access_levels.for_group
%td %td.merge_access_levels-container
= hidden_field_tag "allowed_to_merge_#{protected_branch.id}", merge_access_level&.access_level = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", merge_access_levels.first&.access_level
= dropdown_tag( (merge_access_level&.humanize || 'Select') , = dropdown_tag( (merge_access_levels.first&.humanize || 'Select') ,
options: { toggle_class: 'js-allowed-to-merge', dropdown_class: 'dropdown-menu-selectable js-allowed-to-merge-container capitalize-header', 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 }}) data: { field_name: "allowed_to_merge_#{protected_branch.id}", preselected_items: access_levels_data(merge_access_levels) }})
- if user_merge_access_levels.any? - if user_merge_access_levels.any?
%p.small %p.small
The following The following
...@@ -25,11 +25,11 @@ ...@@ -25,11 +25,11 @@
can also merge into this branch: can also merge into this branch:
#{ group_merge_access_levels.map(&:humanize).to_sentence } #{ group_merge_access_levels.map(&:humanize).to_sentence }
%td %td.push_access_levels-container
= hidden_field_tag "allowed_to_push_#{protected_branch.id}", push_access_level&.access_level = hidden_field_tag "allowed_to_push_#{protected_branch.id}", push_access_levels.first&.access_level
= dropdown_tag( (push_access_level&.humanize || 'Select') , = 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', 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 }}) data: { field_name: "allowed_to_push_#{protected_branch.id}", preselected_items: access_levels_data(push_access_levels) }})
- if user_push_access_levels.any? - if user_push_access_levels.any?
%p.small %p.small
The following The following
......
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(() => {
......
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';
......
...@@ -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