diff options
Diffstat (limited to 'app/assets/javascripts/protected_branches')
5 files changed, 233 insertions, 94 deletions
diff --git a/app/assets/javascripts/protected_branches/constants.js b/app/assets/javascripts/protected_branches/constants.js new file mode 100644 index 00000000000..a17ae6811b7 --- /dev/null +++ b/app/assets/javascripts/protected_branches/constants.js @@ -0,0 +1,18 @@ +export const ACCESS_LEVELS = { + MERGE: 'merge_access_levels', + PUSH: 'push_access_levels', +}; + +export const LEVEL_TYPES = { + ROLE: 'role', + USER: 'user', + GROUP: 'group', +}; + +export const LEVEL_ID_PROP = { + ROLE: 'access_level', + USER: 'user_id', + GROUP: 'group_id', +}; + +export const ACCESS_LEVEL_NONE = 0; diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js deleted file mode 100644 index 41e295387ae..00000000000 --- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js +++ /dev/null @@ -1,28 +0,0 @@ -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(); - }, - }); - } -} diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js b/app/assets/javascripts/protected_branches/protected_branch_create.js index 16ecd5523d6..5ccffe9700e 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_create.js +++ b/app/assets/javascripts/protected_branches/protected_branch_create.js @@ -1,41 +1,62 @@ 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 { deprecatedCreateFlash as 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'))); + } } diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js b/app/assets/javascripts/protected_branches/protected_branch_edit.js index 08d8c9919dd..1f079123081 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit.js +++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js @@ -1,78 +1,165 @@ -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 { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; +import { deprecatedCreateFlash as flash } from '../flash'; 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); + } } diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js index 10253c0febc..6ab9a126e76 100644 --- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js +++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js @@ -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, }); }); } |