From c88891c4ff2b938755015e399358133e81c23708 Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Thu, 26 Jan 2023 16:31:49 +0100 Subject: [PATCH 01/13] Add deploy keys support for Protected tags Contributes to https://gitlab.com/gitlab-org/gitlab/-/issues/325415 * Enable Deploy keys section on UI --- .../projects/settings/access_dropdown.js | 23 ++++- .../projects/settings/constants.js | 1 + .../settings/repository_controller.rb | 1 + .../_create_protected_tag.html.haml | 2 +- .../ee/_create_protected_tag.html.haml | 2 +- ee/spec/features/protected_tags_spec.rb | 10 +++ spec/features/protected_tags_spec.rb | 13 +++ .../projects/settings/access_dropdown_spec.js | 1 + ...rotected_tags_with_deploy_keys_examples.rb | 87 +++++++++++++++++++ 9 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js index 52b8e7e1cd5fb9..7701e860833c87 100644 --- a/app/assets/javascripts/projects/settings/access_dropdown.js +++ b/app/assets/javascripts/projects/settings/access_dropdown.js @@ -11,6 +11,7 @@ export default class AccessDropdown { const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options; this.options = options; this.hasLicense = hasLicense; + this.deployKeysOnProtectedTagsEnabled = gon.features.deployKeyForProtectedTags; this.groups = []; this.accessLevel = accessLevel; this.accessLevelsData = accessLevelsData.roles; @@ -469,6 +470,16 @@ export default class AccessDropdown { } } + if (this.deployKeysOnProtectedTagsEnabled && this.accessLevel === ACCESS_LEVELS.CREATE) { + if (deployKeys.length) { + consolidatedData = consolidatedData.concat( + [{ type: 'divider' }], + [{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }], + deployKeys, + ); + } + } + return consolidatedData; } @@ -505,8 +516,16 @@ export default class AccessDropdown { groupRowEl = this.roleRowHtml(item, isActive); break; case LEVEL_TYPES.DEPLOY_KEY: - groupRowEl = - this.accessLevel === ACCESS_LEVELS.PUSH ? this.deployKeyRowHtml(item, isActive) : ''; + if (this.deployKeysOnProtectedTagsEnabled) { + groupRowEl = + this.accessLevel === ACCESS_LEVELS.PUSH || this.accessLevel === ACCESS_LEVELS.CREATE + ? this.deployKeyRowHtml(item, isActive) + : ''; + } else { + groupRowEl = + this.accessLevel === ACCESS_LEVELS.PUSH ? this.deployKeyRowHtml(item, isActive) : ''; + } + break; case LEVEL_TYPES.GROUP: groupRowEl = this.groupRowHtml(item, isActive); diff --git a/app/assets/javascripts/projects/settings/constants.js b/app/assets/javascripts/projects/settings/constants.js index 9cf1afd334fb26..595cbc9c99138b 100644 --- a/app/assets/javascripts/projects/settings/constants.js +++ b/app/assets/javascripts/projects/settings/constants.js @@ -17,6 +17,7 @@ export const LEVEL_ID_PROP = { export const ACCESS_LEVELS = { MERGE: 'merge_access_levels', PUSH: 'push_access_levels', + CREATE: 'create_access_levels', }; export const ACCESS_LEVEL_NONE = 0; diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index 74d730db026496..5e6284b5d56e28 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -13,6 +13,7 @@ class RepositoryController < Projects::ApplicationController def show push_frontend_feature_flag(:branch_rules, @project) + push_frontend_feature_flag(:deploy_key_for_protected_tags, @project) render_show end diff --git a/app/views/projects/protected_tags/_create_protected_tag.html.haml b/app/views/projects/protected_tags/_create_protected_tag.html.haml index d19a6401fc8295..cc0d127ac35ca9 100644 --- a/app/views/projects/protected_tags/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_create_protected_tag.html.haml @@ -3,7 +3,7 @@ = dropdown_tag('Select', options: { toggle_class: 'js-allowed-to-create wide', dropdown_class: 'dropdown-menu-selectable capitalize-header', - dropdown_qa_selector: 'access_levels_content', + dropdown_qa_selector: 'access_levels_content', dropdown_testid: 'allowed-to-create-dropdown', data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes', qa_selector: 'access_levels_dropdown' }}) = render 'projects/protected_tags/shared/create_protected_tag' diff --git a/ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml b/ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml index 5f52b66b85accb..0edf2a6a4f5fe4 100644 --- a/ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml +++ b/ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml @@ -3,7 +3,7 @@ = dropdown_tag('Select', options: { toggle_class: 'js-allowed-to-create js-multiselect wide', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable capitalize-header', filter: true, - dropdown_qa_selector: 'access_levels_content', + dropdown_qa_selector: 'access_levels_content', dropdown_testid: 'allowed-to-create-dropdown', data: { input_id: 'create_access_levels_attributes', default_label: 'Select', qa_selector: 'access_levels_dropdown' } }) .form-text.text-muted = s_('ProtectedBranch|You can add only groups that have this project shared. %{learn_more_link}').html_safe % { learn_more_link: link_to(_('Learn more.'), help_page_path('user/project/members/share_project_with_groups')) } diff --git a/ee/spec/features/protected_tags_spec.rb b/ee/spec/features/protected_tags_spec.rb index 0df6c4a03ebac3..ee0eb0e6db335f 100644 --- a/ee/spec/features/protected_tags_spec.rb +++ b/ee/spec/features/protected_tags_spec.rb @@ -53,4 +53,14 @@ end end end + + context 'when the users for protected tags feature is on' do + before do + stub_licensed_features(protected_refs_for_users: true) + end + + include_examples 'Deploy keys with protected tags' do + let(:all_dropdown_sections) { ['Roles', 'Users', 'Deploy Keys'] } + end + end end diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb index c2058a5c3456e7..7b72f061478442 100644 --- a/spec/features/protected_tags_spec.rb +++ b/spec/features/protected_tags_spec.rb @@ -101,4 +101,17 @@ include_examples "protected tags > access control > CE" end + + # Pending until the frontend part is ready: + # app/assets/javascripts/protected_tags/protected_tag_create + # app/assets/javascripts/protected_tags/protected_tag_edit + xcontext 'when the users for protected tags feature is off' do + before do + stub_licensed_features(protected_refs_for_users: false) + end + + include_examples 'Deploy keys with protected tags' do + let(:all_dropdown_sections) { ['Roles', 'Deploy keys'] } + end + end end diff --git a/spec/frontend/projects/settings/access_dropdown_spec.js b/spec/frontend/projects/settings/access_dropdown_spec.js index d51360a759706f..20d98bf42120ac 100644 --- a/spec/frontend/projects/settings/access_dropdown_spec.js +++ b/spec/frontend/projects/settings/access_dropdown_spec.js @@ -15,6 +15,7 @@ describe('AccessDropdown', () => { `); const $dropdown = $('#dummy-dropdown'); $dropdown.data('defaultLabel', defaultLabel); + gon.features = { deployKeyForProtectedTags: true }; const options = { $dropdown, accessLevelsData: { diff --git a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb new file mode 100644 index 00000000000000..76be42bed1a05f --- /dev/null +++ b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'Deploy keys with protected tags' do + before do + project.add_maintainer(user) + sign_in(user) + end + + let(:dropdown_sections_minus_deploy_keys) { all_dropdown_sections - ['Deploy Keys'] } + + context 'when deploy keys are enabled to this project' do + let!(:deploy_key_1) { create(:deploy_key, title: 'title 1', projects: [project]) } + let!(:deploy_key_2) { create(:deploy_key, title: 'title 2', projects: [project]) } + + context 'when only one deploy key can push' do + before do + deploy_key_1.deploy_keys_projects.first.update!(can_push: true) + end + + it "shows all dropdown sections in the 'Allowed to create' main dropdown, with only one deploy key" do + visit project_protected_tags_path(project) + + find(".js-allowed-to-create").click + wait_for_requests + + within('[data-testid="allowed-to-create-dropdown"]') do + dropdown_headers = page.all('.dropdown-header').map(&:text) + + expect(dropdown_headers).to contain_exactly(*all_dropdown_sections) + expect(page).to have_content('title 1') + expect(page).not_to have_content('title 2') + end + end + + context 'when feature flag deploy_key_for_protected_tags is disabled' do + before do + stub_feature_flags(deploy_key_for_protected_tags: false) + end + + it "shows all dropdown sections except Deploy keys in the 'Allowed to create' dropdown" do + visit project_protected_tags_path(project) + + find(".js-allowed-to-create").click + wait_for_requests + + within('[data-testid="allowed-to-create-dropdown"]') do + dropdown_headers = page.all('.dropdown-header').map(&:text) + + expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys) + expect(page).not_to have_content('title 1') + expect(page).not_to have_content('title 2') + end + end + end + + it "shows all sections in the 'Allowed to create' update dropdown" do + create(:protected_tag, :no_one_can_create, project: project, name: 'v1.0.0') + + visit project_protected_tags_path(project) + + within(".js-protected-tag-edit-form") do + find(".js-allowed-to-create").click + wait_for_requests + + dropdown_headers = page.all('.dropdown-header').map(&:text) + + expect(dropdown_headers).to contain_exactly(*all_dropdown_sections) + end + end + end + + context 'when no deploy key can push' do + it "just shows all sections but not deploy keys in the 'Allowed to create' dropdown" do + visit project_protected_tags_path(project) + + find(".js-allowed-to-create").click + wait_for_requests + + within('[data-testid="allowed-to-create-dropdown"]') do + dropdown_headers = page.all('.dropdown-header').map(&:text) + + expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys) + end + end + end + end +end -- GitLab From 06df766ab12fb17cd0dc6fba173b2f27fa0ba44a Mon Sep 17 00:00:00 2001 From: Jacques Date: Mon, 6 Mar 2023 15:27:06 +0100 Subject: [PATCH 02/13] Consolidate protected tags dropdown code Merged the EE code into CE --- .../projects/settings/repository/form.js | 4 +- .../protected_tags/protected_tag_create.js | 81 +++++++++++-- .../protected_tags/protected_tag_edit.js | 111 ++++++++++++++---- .../protected_tags/protected_tag_edit_list.js | 4 +- .../settings/repository/show/index.js | 15 +-- .../protected_tags/protected_tag_edit_list.js | 19 --- 6 files changed, 167 insertions(+), 67 deletions(-) delete mode 100644 ee/app/assets/javascripts/protected_tags/protected_tag_edit_list.js diff --git a/app/assets/javascripts/pages/projects/settings/repository/form.js b/app/assets/javascripts/pages/projects/settings/repository/form.js index 380091a35014a2..f64de693188f9c 100644 --- a/app/assets/javascripts/pages/projects/settings/repository/form.js +++ b/app/assets/javascripts/pages/projects/settings/repository/form.js @@ -10,8 +10,8 @@ import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list'; import initSettingsPanels from '~/settings_panels'; export default () => { - new ProtectedTagCreate(); - new ProtectedTagEditList(); + new ProtectedTagCreate({ hasLicense: false }); + new ProtectedTagEditList({ hasLicense: false }); initDeployKeys(); initSettingsPanels(); new ProtectedBranchCreate({ hasLicense: false }); diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js index 75fd11cd0741c4..698f3517aed129 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_create.js +++ b/app/assets/javascripts/protected_tags/protected_tag_create.js @@ -1,12 +1,21 @@ import $ from 'jquery'; -import { __ } from '~/locale'; -import CreateItemDropdown from '../create_item_dropdown'; -import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; +import CreateItemDropdown from '~/create_item_dropdown'; +import { createAlert } from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { s__, __ } from '~/locale'; +import AccessDropdown from '~/projects/settings/access_dropdown'; +import { ACCESS_LEVELS, LEVEL_TYPES } from '~/projects/settings/constants'; export default class ProtectedTagCreate { - constructor() { + constructor(options) { + this.hasLicense = options.hasLicense; this.$form = $('.js-new-protected-tag'); this.buildDropdowns(); + this.bindEvents(); + } + + bindEvents() { + this.$form.on('submit', this.onFormSubmit.bind(this)); } buildDropdowns() { @@ -16,15 +25,14 @@ export default class ProtectedTagCreate { this.onSelectCallback = this.onSelect.bind(this); // Allowed to Create dropdown - this.protectedTagAccessDropdown = new ProtectedTagAccessDropdown({ + this.protectedTagAccessDropdown = new AccessDropdown({ $dropdown: $allowedToCreateDropdown, - data: gon.create_access_levels, + accessLevelsData: gon.create_access_levels, onSelect: this.onSelectCallback, + accessLevel: ACCESS_LEVELS.CREATE, + hasLicense: this.hasLicense, }); - // Select default - $allowedToCreateDropdown.data('deprecatedJQueryDropdown').selectRowAtIndex(0); - // Protected tag dropdown this.createItemDropdown = new CreateItemDropdown({ $dropdown: this.$form.find('.js-protected-tag-select'), @@ -39,7 +47,7 @@ export default class ProtectedTagCreate { onSelect() { // Enable submit button const $tagInput = this.$form.find('input[name="protected_tag[name]"]'); - const $allowedToCreateInput = this.$form.find('#create_access_levels_attributes'); + const $allowedToCreateInput = this.protectedTagAccessDropdown.getSelectedItems(); this.$form .find('button[type="submit"]') @@ -49,4 +57,57 @@ export default class ProtectedTagCreate { static getProtectedTags(term, callback) { callback(gon.open_tags); } + + getFormData() { + const formData = { + authenticity_token: this.$form.find('input[name="authenticity_token"]').val(), + protected_tag: { + name: this.$form.find('input[name="protected_tag[name]"]').val(), + }, + }; + + Object.keys(ACCESS_LEVELS).forEach((level) => { + const accessLevel = ACCESS_LEVELS[level]; + const selectedItems = this.protectedTagAccessDropdown.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, + }); + } else if (item.type === LEVEL_TYPES.DEPLOY_KEY) { + levelAttributes.push({ + deploy_key_id: item.deploy_key_id, + }); + } + }); + + formData.protected_tag[`${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(() => + createAlert({ + message: s__('ProjectSettings|Failed to protect the tag'), + }), + ); + } } diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit.js b/app/assets/javascripts/protected_tags/protected_tag_edit.js index 392cf00d902fdb..1ad02e131ac919 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_edit.js +++ b/app/assets/javascripts/protected_tags/protected_tag_edit.js @@ -1,57 +1,116 @@ -import { createAlert } from '~/alert'; -import axios from '../lib/utils/axios_utils'; +import { find } from 'lodash'; +import { createAlert } from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import AccessDropdown from '~/projects/settings/access_dropdown'; +import { ACCESS_LEVELS, LEVEL_TYPES } from '~/projects/settings/constants'; import { FAILED_TO_UPDATE_TAG_MESSAGE } from './constants'; -import ProtectedTagAccessDropdown from './protected_tag_access_dropdown'; export default class ProtectedTagEdit { constructor(options) { + this.hasLicense = options.hasLicense; + this.hasChanges = false; this.$wrap = options.$wrap; this.$allowedToCreateDropdownButton = this.$wrap.find('.js-allowed-to-create'); - this.onSelectCallback = this.onSelect.bind(this); + + this.$allowedToCreateDropdownContainer = this.$allowedToCreateDropdownButton.closest( + '.create_access_levels-container', + ); this.buildDropdowns(); } buildDropdowns() { // Allowed to create dropdown - this.protectedTagAccessDropdown = new ProtectedTagAccessDropdown({ + this.protectedTagAccessDropdown = new AccessDropdown({ + accessLevel: ACCESS_LEVELS.CREATE, + accessLevelsData: gon.create_access_levels, $dropdown: this.$allowedToCreateDropdownButton, - data: gon.create_access_levels, - onSelect: this.onSelectCallback, + onSelect: this.onSelectOption.bind(this), + onHide: this.onDropdownHide.bind(this), + hasLicense: this.hasLicense, }); } - onSelect() { - const $allowedToCreateInput = this.$wrap.find( - `input[name="${this.$allowedToCreateDropdownButton.data('fieldName')}"]`, - ); + onSelectOption() { + this.hasChanges = true; + } - // Do not update if one dropdown has not selected any option - if (!$allowedToCreateInput.length) return; + onDropdownHide() { + if (!this.hasChanges) { + return; + } - this.$allowedToCreateDropdownButton.disable(); + this.hasChanges = true; + this.updatePermissions(); + } + + updatePermissions() { + const formData = Object.keys(ACCESS_LEVELS).reduce((acc, level) => { + const accessLevelName = ACCESS_LEVELS[level]; + const inputData = this.protectedTagAccessDropdown.getInputData(accessLevelName); + acc[`${accessLevelName}_attributes`] = inputData; + + return acc; + }, {}); axios .patch(this.$wrap.data('url'), { - protected_tag: { - create_access_levels_attributes: [ - { - id: this.$allowedToCreateDropdownButton.data('accessLevelId'), - access_level: $allowedToCreateInput.val(), - }, - ], - }, + protected_tag: formData, }) - .then(() => { - this.$allowedToCreateDropdownButton.enable(); + .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`); + }); }) .catch(() => { - this.$allowedToCreateDropdownButton.enable(); - window.scrollTo({ top: 0, behavior: 'smooth' }); createAlert({ message: FAILED_TO_UPDATE_TAG_MESSAGE, }); }); } + + setSelectedItemsToDropdown(items = []) { + 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.protectedTagAccessDropdown.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.protectedTagAccessDropdown.setSelectedItems(itemsToAdd); + } } diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit_list.js b/app/assets/javascripts/protected_tags/protected_tag_edit_list.js index b35bf4d4606da4..8ceb970bf0367c 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_edit_list.js +++ b/app/assets/javascripts/protected_tags/protected_tag_edit_list.js @@ -4,7 +4,8 @@ import $ from 'jquery'; import ProtectedTagEdit from './protected_tag_edit'; export default class ProtectedTagEditList { - constructor() { + constructor(options) { + this.hasLicense = options.hasLicense; this.$wrap = $('.protected-tags-list'); this.initEditForm(); } @@ -13,6 +14,7 @@ export default class ProtectedTagEditList { this.$wrap.find('.js-protected-tag-edit-form').each((i, el) => { new ProtectedTagEdit({ $wrap: $(el), + hasLicense: this.hasLicense, }); }); } diff --git a/ee/app/assets/javascripts/pages/projects/settings/repository/show/index.js b/ee/app/assets/javascripts/pages/projects/settings/repository/show/index.js index 91ed1f344b505f..6373c72f5b5e6f 100644 --- a/ee/app/assets/javascripts/pages/projects/settings/repository/show/index.js +++ b/ee/app/assets/javascripts/pages/projects/settings/repository/show/index.js @@ -1,14 +1,12 @@ /* eslint-disable no-new */ import ProtectedBranchEditList from 'ee/protected_branches/protected_branch_edit_list'; -import ProtectedTagCreate from 'ee/protected_tags/protected_tag_create'; -import ProtectedTagEditList from 'ee/protected_tags/protected_tag_edit_list'; import initDatePicker from '~/behaviors/date_picker'; import initDeployKeys from '~/deploy_keys'; import fileUpload from '~/lib/utils/file_upload'; import ProtectedBranchCreate from '~/protected_branches/protected_branch_create'; import CEProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list'; -import CEProtectedTagCreate from '~/protected_tags/protected_tag_create'; -import CEProtectedTagEditList from '~/protected_tags/protected_tag_edit_list'; +import ProtectedTagCreate from '~/protected_tags/protected_tag_create'; +import ProtectedTagEditList from '~/protected_tags/protected_tag_edit_list'; import initSearchSettings from '~/search_settings'; import initSettingsPanels from '~/settings_panels'; import UserCallout from '~/user_callout'; @@ -25,14 +23,13 @@ if (document.querySelector('.js-protected-refs-for-users')) { new ProtectedBranchCreate({ hasLicense: true }); new ProtectedBranchEditList(); - new ProtectedTagCreate(); - new ProtectedTagEditList(); + new ProtectedTagCreate({ hasLicense: true }); + new ProtectedTagEditList({ hasLicense: true }); } else { new ProtectedBranchCreate({ hasLicense: false }); new CEProtectedBranchEditList(); - - new CEProtectedTagCreate(); - new CEProtectedTagEditList(); + new ProtectedTagCreate({ hasLicense: false }); + new ProtectedTagEditList({ hasLicense: false }); } const pushPullContainer = document.querySelector('.js-mirror-settings'); diff --git a/ee/app/assets/javascripts/protected_tags/protected_tag_edit_list.js b/ee/app/assets/javascripts/protected_tags/protected_tag_edit_list.js deleted file mode 100644 index b35bf4d4606da4..00000000000000 --- a/ee/app/assets/javascripts/protected_tags/protected_tag_edit_list.js +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable no-new */ - -import $ from 'jquery'; -import ProtectedTagEdit from './protected_tag_edit'; - -export default class ProtectedTagEditList { - constructor() { - this.$wrap = $('.protected-tags-list'); - this.initEditForm(); - } - - initEditForm() { - this.$wrap.find('.js-protected-tag-edit-form').each((i, el) => { - new ProtectedTagEdit({ - $wrap: $(el), - }); - }); - } -} -- GitLab From 9afe2362680e69b4671b72874d02d3d0ed037a44 Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Mon, 6 Mar 2023 19:15:38 +0100 Subject: [PATCH 03/13] Remove feature flag for deploy key display The feature flag is responsible for the stricter verification mode. We are going to allow users to select deploy keys even if the strict verification mode is disabled. --- .../projects/settings/access_dropdown.js | 16 +++++--------- .../settings/repository_controller.rb | 1 - .../projects/settings/access_dropdown_spec.js | 1 - ...rotected_tags_with_deploy_keys_examples.rb | 21 ------------------- 4 files changed, 5 insertions(+), 34 deletions(-) diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js index 7701e860833c87..959cbbf8b8cbc7 100644 --- a/app/assets/javascripts/projects/settings/access_dropdown.js +++ b/app/assets/javascripts/projects/settings/access_dropdown.js @@ -11,7 +11,6 @@ export default class AccessDropdown { const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options; this.options = options; this.hasLicense = hasLicense; - this.deployKeysOnProtectedTagsEnabled = gon.features.deployKeyForProtectedTags; this.groups = []; this.accessLevel = accessLevel; this.accessLevelsData = accessLevelsData.roles; @@ -470,7 +469,7 @@ export default class AccessDropdown { } } - if (this.deployKeysOnProtectedTagsEnabled && this.accessLevel === ACCESS_LEVELS.CREATE) { + if (this.accessLevel === ACCESS_LEVELS.CREATE) { if (deployKeys.length) { consolidatedData = consolidatedData.concat( [{ type: 'divider' }], @@ -516,15 +515,10 @@ export default class AccessDropdown { groupRowEl = this.roleRowHtml(item, isActive); break; case LEVEL_TYPES.DEPLOY_KEY: - if (this.deployKeysOnProtectedTagsEnabled) { - groupRowEl = - this.accessLevel === ACCESS_LEVELS.PUSH || this.accessLevel === ACCESS_LEVELS.CREATE - ? this.deployKeyRowHtml(item, isActive) - : ''; - } else { - groupRowEl = - this.accessLevel === ACCESS_LEVELS.PUSH ? this.deployKeyRowHtml(item, isActive) : ''; - } + groupRowEl = + this.accessLevel === ACCESS_LEVELS.PUSH || this.accessLevel === ACCESS_LEVELS.CREATE + ? this.deployKeyRowHtml(item, isActive) + : ''; break; case LEVEL_TYPES.GROUP: diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index 5e6284b5d56e28..74d730db026496 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -13,7 +13,6 @@ class RepositoryController < Projects::ApplicationController def show push_frontend_feature_flag(:branch_rules, @project) - push_frontend_feature_flag(:deploy_key_for_protected_tags, @project) render_show end diff --git a/spec/frontend/projects/settings/access_dropdown_spec.js b/spec/frontend/projects/settings/access_dropdown_spec.js index 20d98bf42120ac..d51360a759706f 100644 --- a/spec/frontend/projects/settings/access_dropdown_spec.js +++ b/spec/frontend/projects/settings/access_dropdown_spec.js @@ -15,7 +15,6 @@ describe('AccessDropdown', () => { `); const $dropdown = $('#dummy-dropdown'); $dropdown.data('defaultLabel', defaultLabel); - gon.features = { deployKeyForProtectedTags: true }; const options = { $dropdown, accessLevelsData: { diff --git a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb index 76be42bed1a05f..92c65508b2c0c9 100644 --- a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb +++ b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb @@ -32,27 +32,6 @@ end end - context 'when feature flag deploy_key_for_protected_tags is disabled' do - before do - stub_feature_flags(deploy_key_for_protected_tags: false) - end - - it "shows all dropdown sections except Deploy keys in the 'Allowed to create' dropdown" do - visit project_protected_tags_path(project) - - find(".js-allowed-to-create").click - wait_for_requests - - within('[data-testid="allowed-to-create-dropdown"]') do - dropdown_headers = page.all('.dropdown-header').map(&:text) - - expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys) - expect(page).not_to have_content('title 1') - expect(page).not_to have_content('title 2') - end - end - end - it "shows all sections in the 'Allowed to create' update dropdown" do create(:protected_tag, :no_one_can_create, project: project, name: 'v1.0.0') -- GitLab From 10d6cb82bfb1d57fdffaae28bbc962899b941b3a Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Mon, 6 Mar 2023 19:17:50 +0100 Subject: [PATCH 04/13] Use Protected Tag specific constants --- app/assets/javascripts/protected_tags/constants.js | 11 +++++++++++ .../protected_tags/protected_tag_create.js | 2 +- .../javascripts/protected_tags/protected_tag_edit.js | 3 +-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/protected_tags/constants.js b/app/assets/javascripts/protected_tags/constants.js index 3e71ba62877c43..758b820c4c401c 100644 --- a/app/assets/javascripts/protected_tags/constants.js +++ b/app/assets/javascripts/protected_tags/constants.js @@ -1,3 +1,14 @@ import { s__ } from '~/locale'; export const FAILED_TO_UPDATE_TAG_MESSAGE = s__('ProjectSettings|Failed to update tag!'); + +export const ACCESS_LEVELS = { + CREATE: 'create_access_levels', +}; + +export const LEVEL_TYPES = { + ROLE: 'role', + USER: 'user', + GROUP: 'group', + DEPLOY_KEY: 'deploy_key', +}; diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js index 698f3517aed129..4aab1d95fe58d2 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_create.js +++ b/app/assets/javascripts/protected_tags/protected_tag_create.js @@ -4,7 +4,7 @@ import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { s__, __ } from '~/locale'; import AccessDropdown from '~/projects/settings/access_dropdown'; -import { ACCESS_LEVELS, LEVEL_TYPES } from '~/projects/settings/constants'; +import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; export default class ProtectedTagCreate { constructor(options) { diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit.js b/app/assets/javascripts/protected_tags/protected_tag_edit.js index 1ad02e131ac919..6eb7eef09d13c5 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_edit.js +++ b/app/assets/javascripts/protected_tags/protected_tag_edit.js @@ -2,8 +2,7 @@ import { find } from 'lodash'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import AccessDropdown from '~/projects/settings/access_dropdown'; -import { ACCESS_LEVELS, LEVEL_TYPES } from '~/projects/settings/constants'; -import { FAILED_TO_UPDATE_TAG_MESSAGE } from './constants'; +import { ACCESS_LEVELS, LEVEL_TYPES, FAILED_TO_UPDATE_TAG_MESSAGE } from './constants'; export default class ProtectedTagEdit { constructor(options) { -- GitLab From a478e6517a746df57a98d421006d6bb39184e453 Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Mon, 6 Mar 2023 19:20:07 +0100 Subject: [PATCH 05/13] Populate dropdown with correct create access levels --- app/views/projects/protected_tags/_protected_tag.html.haml | 2 +- .../protected_tags/_protected_tag_create_access_levels.haml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml index e0912bf39c0c58..94a473b7f2a7c7 100644 --- a/app/views/projects/protected_tags/_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_protected_tag.html.haml @@ -1,4 +1,4 @@ = render layout: 'projects/protected_tags/shared/protected_tag', locals: { protected_tag: protected_tag } do %td - = render 'projects/protected_tags/protected_tag_create_access_levels', protected_tag: protected_tag, create_access_level: protected_tag.create_access_levels.for_role.first + = render 'projects/protected_tags/protected_tag_create_access_levels', protected_tag: protected_tag, create_access_level: protected_tag.create_access_levels.for_role = render_if_exists 'projects/protected_tags/protected_tag_extra_create_access_levels', protected_tag: protected_tag diff --git a/app/views/projects/protected_tags/_protected_tag_create_access_levels.haml b/app/views/projects/protected_tags/_protected_tag_create_access_levels.haml index 1d4e95651568ef..4da1b862c3447e 100644 --- a/app/views/projects/protected_tags/_protected_tag_create_access_levels.haml +++ b/app/views/projects/protected_tags/_protected_tag_create_access_levels.haml @@ -1,8 +1,8 @@ - protected_tag = local_assigns.fetch(:protected_tag) - create_access_level = local_assigns.fetch(:create_access_level) -- dropdown_label = create_access_level&.humanize || 'Select' +- dropdown_label = create_access_level.first&.humanize || 'Select' -= hidden_field_tag "allowed_to_create_#{protected_tag.id}", create_access_level&.access_level += hidden_field_tag "allowed_to_create_#{protected_tag.id}", create_access_level.first&.access_level = dropdown_tag(dropdown_label, options: { toggle_class: 'js-allowed-to-create', dropdown_class: 'dropdown-menu-selectable capitalize-header js-allowed-to-create-container', - data: { field_name: "allowed_to_create_#{protected_tag.id}", access_level_id: create_access_level&.id }}) + data: { field_name: "allowed_to_create_#{protected_tag.id}", preselected_items: access_levels_data(create_access_level) }}) -- GitLab From ed8d26452e1adb9a76e09820c5408eaaeab3c8e4 Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Mon, 6 Mar 2023 19:20:59 +0100 Subject: [PATCH 06/13] Fix typo and re-enable spec --- spec/features/protected_tags_spec.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb index 7b72f061478442..bf999f059dca61 100644 --- a/spec/features/protected_tags_spec.rb +++ b/spec/features/protected_tags_spec.rb @@ -102,16 +102,13 @@ include_examples "protected tags > access control > CE" end - # Pending until the frontend part is ready: - # app/assets/javascripts/protected_tags/protected_tag_create - # app/assets/javascripts/protected_tags/protected_tag_edit - xcontext 'when the users for protected tags feature is off' do + context 'when the users for protected tags feature is off' do before do stub_licensed_features(protected_refs_for_users: false) end include_examples 'Deploy keys with protected tags' do - let(:all_dropdown_sections) { ['Roles', 'Deploy keys'] } + let(:all_dropdown_sections) { ['Roles', 'Deploy Keys'] } end end end -- GitLab From b44cd65e2799a27dbee15360c780e56444cd6a4c Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Mon, 6 Mar 2023 21:11:28 +0100 Subject: [PATCH 07/13] Fix tests --- spec/features/protected_tags_spec.rb | 24 ++++++++--------- .../access_control_ce_shared_examples.rb | 27 ++++--------------- 2 files changed, 17 insertions(+), 34 deletions(-) diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb index bf999f059dca61..45315f53fd6b0b 100644 --- a/spec/features/protected_tags_spec.rb +++ b/spec/features/protected_tags_spec.rb @@ -16,8 +16,8 @@ it "allows creating explicit protected tags" do visit project_protected_tags_path(project) set_protected_tag_name('some-tag') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") { expect(page).to have_content('some-tag') } expect(ProtectedTag.count).to eq(1) @@ -30,8 +30,8 @@ visit project_protected_tags_path(project) set_protected_tag_name('some-tag') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") { expect(page).to have_content(commit.id[0..7]) } end @@ -39,8 +39,8 @@ it "displays an error message if the named tag does not exist" do visit project_protected_tags_path(project) set_protected_tag_name('some-tag') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") { expect(page).to have_content('tag was removed') } end @@ -50,8 +50,8 @@ it "allows creating protected tags with a wildcard" do visit project_protected_tags_path(project) set_protected_tag_name('*-stable') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") { expect(page).to have_content('*-stable') } expect(ProtectedTag.count).to eq(1) @@ -64,8 +64,8 @@ visit project_protected_tags_path(project) set_protected_tag_name('*-stable') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect within(".protected-tags-list") do expect(page).to have_content("Protected tags (2)") @@ -80,8 +80,8 @@ visit project_protected_tags_path(project) set_protected_tag_name('*-stable') - set_allowed_to('create') if Gitlab.ee? - click_on "Protect" + set_allowed_to('create') + click_on_protect visit project_protected_tags_path(project) click_on "2 matching tags" diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb index 8666c19481c998..6aa9647bcec9ca 100644 --- a/spec/support/protected_tags/access_control_ce_shared_examples.rb +++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb @@ -6,18 +6,8 @@ visit project_protected_tags_path(project) set_protected_tag_name('master') - - within('.js-new-protected-tag') do - allowed_to_create_button = find(".js-allowed-to-create") - - unless allowed_to_create_button.text == access_type_name - allowed_to_create_button.click - find('.create_access_levels-container .dropdown-menu li', match: :first) - within('.create_access_levels-container .dropdown-menu') { click_on access_type_name } - end - end - - click_on "Protect" + set_allowed_to('create', access_type_name) + click_on_protect expect(ProtectedTag.count).to eq(1) expect(ProtectedTag.last.create_access_levels.map(&:access_level)).to eq([access_type_id]) @@ -27,19 +17,12 @@ visit project_protected_tags_path(project) set_protected_tag_name('master') - - click_on "Protect" + set_allowed_to('create', 'No one') + click_on_protect expect(ProtectedTag.count).to eq(1) - within(".protected-tags-list") do - find(".js-allowed-to-create").click - - within('.js-allowed-to-create-container') do - expect(first("li")).to have_content("Roles") - click_on access_type_name - end - end + set_allowed_to('create', access_type_name, form: '.protected-tags-list') wait_for_requests -- GitLab From e2dda4c4edb20d60e732956dd37f4f66eba536f2 Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Tue, 7 Mar 2023 17:05:26 +0100 Subject: [PATCH 08/13] Enable multiselect for tag create dropdown --- .../projects/settings/components/access_dropdown.vue | 2 +- .../protected_tags/_protected_tag_create_access_levels.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/projects/settings/components/access_dropdown.vue b/app/assets/javascripts/projects/settings/components/access_dropdown.vue index 627914ae2b1195..87fe592f22832b 100644 --- a/app/assets/javascripts/projects/settings/components/access_dropdown.vue +++ b/app/assets/javascripts/projects/settings/components/access_dropdown.vue @@ -86,7 +86,7 @@ export default { return groupBy(this.preselectedItems, 'type'); }, showDeployKeys() { - return this.accessLevel === ACCESS_LEVELS.PUSH && this.deployKeys.length; + return (this.accessLevel === ACCESS_LEVELS.PUSH || this.accessLevel === ACCESS_LEVELS.CREATE) && this.deployKeys.length; }, toggleLabel() { const counts = Object.entries(this.selected).reduce((acc, [key, value]) => { diff --git a/app/views/projects/protected_tags/_protected_tag_create_access_levels.haml b/app/views/projects/protected_tags/_protected_tag_create_access_levels.haml index 4da1b862c3447e..30b9e3e9005aa3 100644 --- a/app/views/projects/protected_tags/_protected_tag_create_access_levels.haml +++ b/app/views/projects/protected_tags/_protected_tag_create_access_levels.haml @@ -4,5 +4,5 @@ = hidden_field_tag "allowed_to_create_#{protected_tag.id}", create_access_level.first&.access_level = dropdown_tag(dropdown_label, - options: { toggle_class: 'js-allowed-to-create', dropdown_class: 'dropdown-menu-selectable capitalize-header js-allowed-to-create-container', + options: { toggle_class: 'js-allowed-to-create js-multiselect', dropdown_class: 'dropdown-menu-selectable capitalize-header js-allowed-to-create-container', data: { field_name: "allowed_to_create_#{protected_tag.id}", preselected_items: access_levels_data(create_access_level) }}) -- GitLab From 2013326f2c3dde8560efcb02db3ecdc1525614de Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Tue, 7 Mar 2023 17:47:31 +0100 Subject: [PATCH 09/13] Fix missing class --- .../projects/settings/components/access_dropdown.vue | 5 ++++- .../projects/protected_tags/_create_protected_tag.html.haml | 2 +- app/views/projects/protected_tags/_protected_tag.html.haml | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/projects/settings/components/access_dropdown.vue b/app/assets/javascripts/projects/settings/components/access_dropdown.vue index 87fe592f22832b..08a1c586f69576 100644 --- a/app/assets/javascripts/projects/settings/components/access_dropdown.vue +++ b/app/assets/javascripts/projects/settings/components/access_dropdown.vue @@ -86,7 +86,10 @@ export default { return groupBy(this.preselectedItems, 'type'); }, showDeployKeys() { - return (this.accessLevel === ACCESS_LEVELS.PUSH || this.accessLevel === ACCESS_LEVELS.CREATE) && this.deployKeys.length; + return ( + (this.accessLevel === ACCESS_LEVELS.PUSH || this.accessLevel === ACCESS_LEVELS.CREATE) && + this.deployKeys.length + ); }, toggleLabel() { const counts = Object.entries(this.selected).reduce((acc, [key, value]) => { diff --git a/app/views/projects/protected_tags/_create_protected_tag.html.haml b/app/views/projects/protected_tags/_create_protected_tag.html.haml index cc0d127ac35ca9..ef3974b04b5bc9 100644 --- a/app/views/projects/protected_tags/_create_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_create_protected_tag.html.haml @@ -1,7 +1,7 @@ - content_for :create_access_levels do .create_access_levels-container = dropdown_tag('Select', - options: { toggle_class: 'js-allowed-to-create wide', + options: { toggle_class: 'js-allowed-to-create js-multiselect wide', dropdown_class: 'dropdown-menu-selectable capitalize-header', dropdown_qa_selector: 'access_levels_content', dropdown_testid: 'allowed-to-create-dropdown', data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes', qa_selector: 'access_levels_dropdown' }}) diff --git a/app/views/projects/protected_tags/_protected_tag.html.haml b/app/views/projects/protected_tags/_protected_tag.html.haml index 94a473b7f2a7c7..68e4a5e97a361f 100644 --- a/app/views/projects/protected_tags/_protected_tag.html.haml +++ b/app/views/projects/protected_tags/_protected_tag.html.haml @@ -1,4 +1,4 @@ = render layout: 'projects/protected_tags/shared/protected_tag', locals: { protected_tag: protected_tag } do - %td + %td.create_access_levels-container = render 'projects/protected_tags/protected_tag_create_access_levels', protected_tag: protected_tag, create_access_level: protected_tag.create_access_levels.for_role = render_if_exists 'projects/protected_tags/protected_tag_extra_create_access_levels', protected_tag: protected_tag -- GitLab From 2bb958e54bef4e3b877650830afc2c521eace98f Mon Sep 17 00:00:00 2001 From: Jacques Date: Wed, 8 Mar 2023 10:20:22 +0100 Subject: [PATCH 10/13] Use alert instead of flash for error messages Updated the error message to use alert --- .../protected_tags/protected_tag_edit.js | 2 +- .../protected_tags/protected_tag_create.js | 107 ---------------- .../protected_tags/protected_tag_edit.js | 114 ------------------ 3 files changed, 1 insertion(+), 222 deletions(-) delete mode 100644 ee/app/assets/javascripts/protected_tags/protected_tag_create.js delete mode 100644 ee/app/assets/javascripts/protected_tags/protected_tag_edit.js diff --git a/app/assets/javascripts/protected_tags/protected_tag_edit.js b/app/assets/javascripts/protected_tags/protected_tag_edit.js index 6eb7eef09d13c5..4fa3ac3be4b4ef 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_edit.js +++ b/app/assets/javascripts/protected_tags/protected_tag_edit.js @@ -1,5 +1,5 @@ import { find } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import AccessDropdown from '~/projects/settings/access_dropdown'; import { ACCESS_LEVELS, LEVEL_TYPES, FAILED_TO_UPDATE_TAG_MESSAGE } from './constants'; diff --git a/ee/app/assets/javascripts/protected_tags/protected_tag_create.js b/ee/app/assets/javascripts/protected_tags/protected_tag_create.js deleted file mode 100644 index 5f5eed1240d266..00000000000000 --- a/ee/app/assets/javascripts/protected_tags/protected_tag_create.js +++ /dev/null @@ -1,107 +0,0 @@ -import $ from 'jquery'; -import CreateItemDropdown from '~/create_item_dropdown'; -import { createAlert } from '~/alert'; -import axios from '~/lib/utils/axios_utils'; -import { s__, __ } from '~/locale'; -import AccessDropdown from '~/projects/settings/access_dropdown'; -import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; - -export default class ProtectedTagCreate { - constructor() { - this.$form = $('.js-new-protected-tag'); - this.buildDropdowns(); - this.$tagInput = this.$form.find('input[name="protected_tag[name]"]'); - this.bindEvents(); - } - - bindEvents() { - this.$form.on('submit', this.onFormSubmit.bind(this)); - } - - buildDropdowns() { - const $allowedToCreateDropdown = this.$form.find('.js-allowed-to-create'); - - // Cache callback - this.onSelectCallback = this.onSelect.bind(this); - - // Allowed to Create dropdown - this[`${ACCESS_LEVELS.CREATE}_dropdown`] = new AccessDropdown({ - $dropdown: $allowedToCreateDropdown, - accessLevelsData: gon.create_access_levels, - onSelect: this.onSelectCallback, - accessLevel: ACCESS_LEVELS.CREATE, - }); - - // Protected tag dropdown - this.createItemDropdown = new CreateItemDropdown({ - $dropdown: this.$form.find('.js-protected-tag-select'), - defaultToggleLabel: __('Protected Tag'), - fieldName: 'protected_tag[name]', - onSelect: this.onSelectCallback, - getData: ProtectedTagCreate.getProtectedTags, - }); - } - - // Enable submit button after selecting an option - onSelect() { - const $allowedToCreate = this[`${ACCESS_LEVELS.CREATE}_dropdown`].getSelectedItems(); - const toggle = !( - this.$form.find('input[name="protected_tag[name]"]').val() && $allowedToCreate.length - ); - - this.$form.find('button[type="submit"]').attr('disabled', toggle); - } - - static getProtectedTags(term, callback) { - callback(gon.open_tags); - } - - getFormData() { - const formData = { - authenticity_token: this.$form.find('input[name="authenticity_token"]').val(), - protected_tag: { - name: this.$form.find('input[name="protected_tag[name]"]').val(), - }, - }; - - Object.keys(ACCESS_LEVELS).forEach((level) => { - const accessLevel = ACCESS_LEVELS[level]; - const selectedItems = this[`${ACCESS_LEVELS.CREATE}_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_tag[`${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(() => - createAlert({ - message: s__('ProjectSettings|Failed to protect the tag'), - }), - ); - } -} diff --git a/ee/app/assets/javascripts/protected_tags/protected_tag_edit.js b/ee/app/assets/javascripts/protected_tags/protected_tag_edit.js deleted file mode 100644 index 133d44621ebaad..00000000000000 --- a/ee/app/assets/javascripts/protected_tags/protected_tag_edit.js +++ /dev/null @@ -1,114 +0,0 @@ -import { find } from 'lodash'; -import { createAlert } from '~/alert'; -import axios from '~/lib/utils/axios_utils'; -import AccessDropdown from '~/projects/settings/access_dropdown'; -import { FAILED_TO_UPDATE_TAG_MESSAGE } from '~/protected_tags/constants'; -import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; - -export default class ProtectedTagEdit { - constructor(options) { - this.hasChanges = false; - this.$wrap = options.$wrap; - this.$allowedToCreateDropdownButton = this.$wrap.find('.js-allowed-to-create'); - - this.$allowedToCreateDropdownContainer = this.$allowedToCreateDropdownButton.closest( - '.create_access_levels-container', - ); - - this.buildDropdowns(); - } - - buildDropdowns() { - // Allowed to create dropdown - this[`${ACCESS_LEVELS.CREATE}_dropdown`] = new AccessDropdown({ - accessLevel: ACCESS_LEVELS.CREATE, - accessLevelsData: gon.create_access_levels, - $dropdown: this.$allowedToCreateDropdownButton, - 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_tag: 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`); - }); - }) - .catch(() => { - window.scrollTo({ top: 0, behavior: 'smooth' }); - createAlert({ - message: FAILED_TO_UPDATE_TAG_MESSAGE, - }); - }); - } - - 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); - } -} -- GitLab From ccaaedba669ed5975be1609a3d5f16ead5261e28 Mon Sep 17 00:00:00 2001 From: Vasilii Iakliushin Date: Tue, 14 Mar 2023 14:58:21 +0100 Subject: [PATCH 11/13] Apply code review suggestion --- .../features/protected_tags_with_deploy_keys_examples.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb index 92c65508b2c0c9..cc0984b62262de 100644 --- a/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb +++ b/spec/support/shared_examples/features/protected_tags_with_deploy_keys_examples.rb @@ -1,11 +1,6 @@ # frozen_string_literal: true RSpec.shared_examples 'Deploy keys with protected tags' do - before do - project.add_maintainer(user) - sign_in(user) - end - let(:dropdown_sections_minus_deploy_keys) { all_dropdown_sections - ['Deploy Keys'] } context 'when deploy keys are enabled to this project' do -- GitLab From aa2476263cc1fac0432b9021f0ce992c57d8032c Mon Sep 17 00:00:00 2001 From: Jacques Date: Wed, 15 Mar 2023 11:00:13 +0100 Subject: [PATCH 12/13] Add specs for conditional rendering of deploy keys Added specs to test that deploy keys are rendered based on accessLevel --- .../components/new_access_dropdown_spec.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js index 26297d0c3ffaa6..667399cc979e91 100644 --- a/spec/frontend/projects/settings/components/new_access_dropdown_spec.js +++ b/spec/frontend/projects/settings/components/new_access_dropdown_spec.js @@ -98,6 +98,7 @@ describe('Access Level Dropdown', () => { const findAllDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem); const findAllDropdownHeaders = () => findDropdown().findAllComponents(GlDropdownSectionHeader); const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType); + const findDeployKeyDropdownItem = () => wrapper.findByTestId('deploy_key-dropdown-item'); const findDropdownItemWithText = (items, text) => items.filter((item) => item.text().includes(text)).at(0); @@ -142,6 +143,21 @@ describe('Access Level Dropdown', () => { it('renders dropdown item for each access level type', () => { expect(findAllDropdownItems()).toHaveLength(12); }); + + it.each` + accessLevel | shouldRenderDeployKeyItems + ${ACCESS_LEVELS.PUSH} | ${true} + ${ACCESS_LEVELS.CREATE} | ${true} + ${ACCESS_LEVELS.MERGE} | ${false} + `( + 'conditionally renders deploy keys based on access levels', + async ({ accessLevel, shouldRenderDeployKeyItems }) => { + createComponent({ accessLevel }); + await waitForPromises(); + + expect(findDeployKeyDropdownItem().exists()).toBe(shouldRenderDeployKeyItems); + }, + ); }); describe('toggleLabel', () => { -- GitLab From 6f6c79010c58dab83adf1e9552f9c681f854b41f Mon Sep 17 00:00:00 2001 From: Jacques Date: Thu, 16 Mar 2023 18:46:07 +0100 Subject: [PATCH 13/13] Address review comments + replace alert import Addressed code review comments and replaced flash with alert --- .../projects/settings/access_dropdown.js | 14 ++++++-------- .../protected_tags/protected_tag_create.js | 6 +++--- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js index 959cbbf8b8cbc7..71c9e58042003a 100644 --- a/app/assets/javascripts/projects/settings/access_dropdown.js +++ b/app/assets/javascripts/projects/settings/access_dropdown.js @@ -469,14 +469,12 @@ export default class AccessDropdown { } } - if (this.accessLevel === ACCESS_LEVELS.CREATE) { - if (deployKeys.length) { - consolidatedData = consolidatedData.concat( - [{ type: 'divider' }], - [{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }], - deployKeys, - ); - } + if (this.accessLevel === ACCESS_LEVELS.CREATE && deployKeys.length) { + consolidatedData = consolidatedData.concat( + [{ type: 'divider' }], + [{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }], + deployKeys, + ); } return consolidatedData; diff --git a/app/assets/javascripts/protected_tags/protected_tag_create.js b/app/assets/javascripts/protected_tags/protected_tag_create.js index 4aab1d95fe58d2..365b9a3b142d59 100644 --- a/app/assets/javascripts/protected_tags/protected_tag_create.js +++ b/app/assets/javascripts/protected_tags/protected_tag_create.js @@ -1,14 +1,14 @@ import $ from 'jquery'; import CreateItemDropdown from '~/create_item_dropdown'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { s__, __ } from '~/locale'; import AccessDropdown from '~/projects/settings/access_dropdown'; import { ACCESS_LEVELS, LEVEL_TYPES } from './constants'; export default class ProtectedTagCreate { - constructor(options) { - this.hasLicense = options.hasLicense; + constructor({ hasLicense }) { + this.hasLicense = hasLicense; this.$form = $('.js-new-protected-tag'); this.buildDropdowns(); this.bindEvents(); -- GitLab