Commit e55ffeae authored by Etienne Baqué's avatar Etienne Baqué Committed by Olena Horal-Koretska

Added Deploy Keys option in protected branches

Updated AccessDropdown component.
Updated Protected Branches view.
parent 653e3b2e
...@@ -2,8 +2,8 @@ ...@@ -2,8 +2,8 @@
import { escape, find, countBy } from 'lodash'; import { escape, find, countBy } from 'lodash';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as Flash } from '~/flash'; import { deprecatedCreateFlash as Flash } from '~/flash';
import { n__, s__, __ } from '~/locale'; import { n__, s__, __, sprintf } from '~/locale';
import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVEL_NONE } from './constants'; import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVELS, ACCESS_LEVEL_NONE } from './constants';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
export default class AccessDropdown { export default class AccessDropdown {
...@@ -11,6 +11,7 @@ export default class AccessDropdown { ...@@ -11,6 +11,7 @@ export default class AccessDropdown {
const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options; const { $dropdown, accessLevel, accessLevelsData, hasLicense = true } = options;
this.options = options; this.options = options;
this.hasLicense = hasLicense; this.hasLicense = hasLicense;
this.deployKeysOnProtectedBranchesEnabled = gon.features.deployKeysOnProtectedBranches;
this.groups = []; this.groups = [];
this.accessLevel = accessLevel; this.accessLevel = accessLevel;
this.accessLevelsData = accessLevelsData.roles; this.accessLevelsData = accessLevelsData.roles;
...@@ -18,6 +19,7 @@ export default class AccessDropdown { ...@@ -18,6 +19,7 @@ export default class AccessDropdown {
this.$wrap = this.$dropdown.closest(`.${this.accessLevel}-container`); this.$wrap = this.$dropdown.closest(`.${this.accessLevel}-container`);
this.usersPath = '/-/autocomplete/users.json'; this.usersPath = '/-/autocomplete/users.json';
this.groupsPath = '/-/autocomplete/project_groups.json'; this.groupsPath = '/-/autocomplete/project_groups.json';
this.deployKeysPath = '/-/autocomplete/deploy_keys_with_owners.json';
this.defaultLabel = this.$dropdown.data('defaultLabel'); this.defaultLabel = this.$dropdown.data('defaultLabel');
this.setSelectedItems([]); this.setSelectedItems([]);
...@@ -146,6 +148,8 @@ export default class AccessDropdown { ...@@ -146,6 +148,8 @@ export default class AccessDropdown {
obj.access_level = item.access_level; obj.access_level = item.access_level;
} else if (item.type === LEVEL_TYPES.USER) { } else if (item.type === LEVEL_TYPES.USER) {
obj.user_id = item.user_id; obj.user_id = item.user_id;
} else if (item.type === LEVEL_TYPES.DEPLOY_KEY) {
obj.deploy_key_id = item.deploy_key_id;
} else if (item.type === LEVEL_TYPES.GROUP) { } else if (item.type === LEVEL_TYPES.GROUP) {
obj.group_id = item.group_id; obj.group_id = item.group_id;
} }
...@@ -177,6 +181,9 @@ export default class AccessDropdown { ...@@ -177,6 +181,9 @@ export default class AccessDropdown {
case LEVEL_TYPES.GROUP: case LEVEL_TYPES.GROUP:
comparator = LEVEL_ID_PROP.GROUP; comparator = LEVEL_ID_PROP.GROUP;
break; break;
case LEVEL_TYPES.DEPLOY_KEY:
comparator = LEVEL_ID_PROP.DEPLOY_KEY;
break;
case LEVEL_TYPES.USER: case LEVEL_TYPES.USER:
comparator = LEVEL_ID_PROP.USER; comparator = LEVEL_ID_PROP.USER;
break; break;
...@@ -218,6 +225,11 @@ export default class AccessDropdown { ...@@ -218,6 +225,11 @@ export default class AccessDropdown {
group_id: selectedItem.id, group_id: selectedItem.id,
type: LEVEL_TYPES.GROUP, type: LEVEL_TYPES.GROUP,
}; };
} else if (selectedItem.type === LEVEL_TYPES.DEPLOY_KEY) {
itemToAdd = {
deploy_key_id: selectedItem.id,
type: LEVEL_TYPES.DEPLOY_KEY,
};
} }
this.items.push(itemToAdd); this.items.push(itemToAdd);
...@@ -233,11 +245,12 @@ export default class AccessDropdown { ...@@ -233,11 +245,12 @@ export default class AccessDropdown {
return true; return true;
} }
if (item.type === LEVEL_TYPES.USER && item.user_id === itemToDelete.id) { if (
index = i; (item.type === LEVEL_TYPES.USER && item.user_id === itemToDelete.id) ||
} else if (item.type === LEVEL_TYPES.ROLE && item.access_level === itemToDelete.id) { (item.type === LEVEL_TYPES.ROLE && item.access_level === itemToDelete.id) ||
index = i; (item.type === LEVEL_TYPES.DEPLOY_KEY && item.deploy_key_id === itemToDelete.id) ||
} else if (item.type === LEVEL_TYPES.GROUP && item.group_id === itemToDelete.id) { (item.type === LEVEL_TYPES.GROUP && item.group_id === itemToDelete.id)
) {
index = i; index = i;
} }
...@@ -289,6 +302,10 @@ export default class AccessDropdown { ...@@ -289,6 +302,10 @@ export default class AccessDropdown {
labelPieces.push(n__('1 user', '%d users', counts[LEVEL_TYPES.USER])); labelPieces.push(n__('1 user', '%d users', counts[LEVEL_TYPES.USER]));
} }
if (counts[LEVEL_TYPES.DEPLOY_KEY] > 0) {
labelPieces.push(n__('1 deploy key', '%d deploy keys', counts[LEVEL_TYPES.DEPLOY_KEY]));
}
if (counts[LEVEL_TYPES.GROUP] > 0) { if (counts[LEVEL_TYPES.GROUP] > 0) {
labelPieces.push(n__('1 group', '%d groups', counts[LEVEL_TYPES.GROUP])); labelPieces.push(n__('1 group', '%d groups', counts[LEVEL_TYPES.GROUP]));
} }
...@@ -299,20 +316,31 @@ export default class AccessDropdown { ...@@ -299,20 +316,31 @@ export default class AccessDropdown {
getData(query, callback) { getData(query, callback) {
if (this.hasLicense) { if (this.hasLicense) {
Promise.all([ Promise.all([
this.getDeployKeys(query),
this.getUsers(query), this.getUsers(query),
this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(), this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(),
]) ])
.then(([usersResponse, groupsResponse]) => { .then(([deployKeysResponse, usersResponse, groupsResponse]) => {
this.groupsData = groupsResponse; this.groupsData = groupsResponse;
callback(this.consolidateData(usersResponse.data, groupsResponse.data)); callback(
this.consolidateData(deployKeysResponse.data, usersResponse.data, groupsResponse.data),
);
}) })
.catch(() => Flash(__('Failed to load groups & users.'))); .catch(() => {
if (this.deployKeysOnProtectedBranchesEnabled) {
Flash(__('Failed to load groups, users and deploy keys.'));
} else {
Flash(__('Failed to load groups & users.'));
}
});
} else { } else {
callback(this.consolidateData()); this.getDeployKeys(query)
.then(deployKeysResponse => callback(this.consolidateData(deployKeysResponse.data)))
.catch(() => Flash(__('Failed to load deploy keys.')));
} }
} }
consolidateData(usersResponse = [], groupsResponse = []) { consolidateData(deployKeysResponse, usersResponse = [], groupsResponse = []) {
let consolidatedData = []; let consolidatedData = [];
// ID property is handled differently locally from the server // ID property is handled differently locally from the server
...@@ -328,6 +356,10 @@ export default class AccessDropdown { ...@@ -328,6 +356,10 @@ export default class AccessDropdown {
// For Users // For Users
// In dropdown: `id` // In dropdown: `id`
// For submit: `user_id` // For submit: `user_id`
//
// For Deploy Keys
// In dropdown: `id`
// For submit: `deploy_key_id`
/* /*
* Build roles * Build roles
...@@ -410,6 +442,38 @@ export default class AccessDropdown { ...@@ -410,6 +442,38 @@ export default class AccessDropdown {
} }
} }
if (this.deployKeysOnProtectedBranchesEnabled) {
const deployKeys = deployKeysResponse.map(response => {
const {
id,
fingerprint,
title,
owner: { avatar_url, name, username },
} = response;
const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`;
return {
id,
title: title.concat(' ', shortFingerprint),
avatar_url,
fullname: name,
username,
type: LEVEL_TYPES.DEPLOY_KEY,
};
});
if (this.accessLevel === ACCESS_LEVELS.PUSH) {
if (deployKeys.length) {
consolidatedData = consolidatedData.concat(
[{ type: 'divider' }],
[{ type: 'header', content: s__('AccessDropdown|Deploy Keys') }],
deployKeys,
);
}
}
}
return consolidatedData; return consolidatedData;
} }
...@@ -433,6 +497,22 @@ export default class AccessDropdown { ...@@ -433,6 +497,22 @@ export default class AccessDropdown {
}); });
} }
getDeployKeys(query) {
if (this.deployKeysOnProtectedBranchesEnabled) {
return axios.get(this.buildUrl(gon.relative_url_root, this.deployKeysPath), {
params: {
search: query,
per_page: 20,
active: true,
project_id: gon.current_project_id,
push_code: true,
},
});
}
return Promise.resolve({ data: [] });
}
buildUrl(urlRoot, url) { buildUrl(urlRoot, url) {
let newUrl; let newUrl;
if (urlRoot != null) { if (urlRoot != null) {
...@@ -454,6 +534,9 @@ export default class AccessDropdown { ...@@ -454,6 +534,9 @@ export default class AccessDropdown {
case LEVEL_TYPES.ROLE: case LEVEL_TYPES.ROLE:
criteria = { access_level: item.id }; criteria = { access_level: item.id };
break; break;
case LEVEL_TYPES.DEPLOY_KEY:
criteria = { deploy_key_id: item.id };
break;
case LEVEL_TYPES.GROUP: case LEVEL_TYPES.GROUP:
criteria = { group_id: item.id }; criteria = { group_id: item.id };
break; break;
...@@ -470,6 +553,10 @@ export default class AccessDropdown { ...@@ -470,6 +553,10 @@ export default class AccessDropdown {
case LEVEL_TYPES.ROLE: case LEVEL_TYPES.ROLE:
groupRowEl = this.roleRowHtml(item, isActive); groupRowEl = this.roleRowHtml(item, isActive);
break; break;
case LEVEL_TYPES.DEPLOY_KEY:
groupRowEl =
this.accessLevel === ACCESS_LEVELS.PUSH ? this.deployKeyRowHtml(item, isActive) : '';
break;
case LEVEL_TYPES.GROUP: case LEVEL_TYPES.GROUP:
groupRowEl = this.groupRowHtml(item, isActive); groupRowEl = this.groupRowHtml(item, isActive);
break; break;
...@@ -495,6 +582,31 @@ export default class AccessDropdown { ...@@ -495,6 +582,31 @@ export default class AccessDropdown {
`; `;
} }
deployKeyRowHtml(key, isActive) {
const isActiveClass = isActive || '';
return `
<li>
<a href="#" class="${isActiveClass}">
<strong>${key.title}</strong>
<p>
${sprintf(
__('Owned by %{image_tag}'),
{
image_tag: `<img src="${key.avatar_url}" class="avatar avatar-inline s26" width="30">`,
},
false,
)}
<strong class="dropdown-menu-user-full-name gl-display-inline">${escape(
key.fullname,
)}</strong>
<span class="dropdown-menu-user-username gl-display-inline">${key.username}</span>
</p>
</a>
</li>
`;
}
groupRowHtml(group, isActive) { groupRowHtml(group, isActive) {
const isActiveClass = isActive || ''; const isActiveClass = isActive || '';
const avatarEl = group.avatar_url const avatarEl = group.avatar_url
......
export const LEVEL_TYPES = { export const LEVEL_TYPES = {
ROLE: 'role', ROLE: 'role',
USER: 'user', USER: 'user',
DEPLOY_KEY: 'deploy_key',
GROUP: 'group', GROUP: 'group',
}; };
export const LEVEL_ID_PROP = { export const LEVEL_ID_PROP = {
ROLE: 'access_level', ROLE: 'access_level',
USER: 'user_id', USER: 'user_id',
DEPLOY_KEY: 'deploy_key_id',
GROUP: 'group_id', GROUP: 'group_id',
}; };
export const ACCESS_LEVELS = {
MERGE: 'merge_access_levels',
PUSH: 'push_access_levels',
};
export const ACCESS_LEVEL_NONE = 0; export const ACCESS_LEVEL_NONE = 0;
...@@ -7,12 +7,14 @@ export const LEVEL_TYPES = { ...@@ -7,12 +7,14 @@ export const LEVEL_TYPES = {
ROLE: 'role', ROLE: 'role',
USER: 'user', USER: 'user',
GROUP: 'group', GROUP: 'group',
DEPLOY_KEY: 'deploy_key',
}; };
export const LEVEL_ID_PROP = { export const LEVEL_ID_PROP = {
ROLE: 'access_level', ROLE: 'access_level',
USER: 'user_id', USER: 'user_id',
GROUP: 'group_id', GROUP: 'group_id',
DEPLOY_KEY: 'deploy_key_id',
}; };
export const ACCESS_LEVEL_NONE = 0; export const ACCESS_LEVEL_NONE = 0;
...@@ -108,6 +108,10 @@ export default class ProtectedBranchCreate { ...@@ -108,6 +108,10 @@ export default class ProtectedBranchCreate {
levelAttributes.push({ levelAttributes.push({
group_id: item.group_id, group_id: item.group_id,
}); });
} else if (item.type === LEVEL_TYPES.DEPLOY_KEY) {
levelAttributes.push({
deploy_key_id: item.deploy_key_id,
});
} }
}); });
......
...@@ -62,7 +62,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController ...@@ -62,7 +62,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
end end
def access_level_attributes def access_level_attributes
%i[access_level id _destroy] %i[access_level id _destroy deploy_key_id]
end end
end end
......
...@@ -7,6 +7,7 @@ module Projects ...@@ -7,6 +7,7 @@ module Projects
before_action :define_variables, only: [:create_deploy_token] before_action :define_variables, only: [:create_deploy_token]
before_action do before_action do
push_frontend_feature_flag(:ajax_new_deploy_token, @project) push_frontend_feature_flag(:ajax_new_deploy_token, @project)
push_frontend_feature_flag(:deploy_keys_on_protected_branches, @project)
end end
def show def show
...@@ -125,6 +126,7 @@ module Projects ...@@ -125,6 +126,7 @@ module Projects
gon.push(protectable_tags_for_dropdown) gon.push(protectable_tags_for_dropdown)
gon.push(protectable_branches_for_dropdown) gon.push(protectable_branches_for_dropdown)
gon.push(access_levels_options) gon.push(access_levels_options)
gon.push(current_project_id: project.id) if project
end end
end end
end end
......
- select_mode_for_dropdown = Feature.enabled?(:deploy_keys_on_protected_branches, @project) ? 'js-multiselect' : ''
- content_for :merge_access_levels do - content_for :merge_access_levels do
.merge_access_levels-container .merge_access_levels-container
= dropdown_tag('Select', = dropdown_tag('Select',
...@@ -7,7 +9,7 @@ ...@@ -7,7 +9,7 @@
- content_for :push_access_levels do - content_for :push_access_levels do
.push_access_levels-container .push_access_levels-container
= dropdown_tag('Select', = dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push-select wide', options: { toggle_class: "js-allowed-to-push qa-allowed-to-push-select #{select_mode_for_dropdown} wide",
dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown rspec-allowed-to-push-dropdown capitalize-header', dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown rspec-allowed-to-push-dropdown capitalize-header',
data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }}) data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
......
---
name: deploy_keys_on_protected_branches
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35638
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/247866
group: group::progressive delivery
type: development
default_enabled: false
...@@ -33,12 +33,6 @@ module EE ...@@ -33,12 +33,6 @@ module EE
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables # rubocop:enable Gitlab/ModuleWithInstanceVariables
def load_gon_index
super
gon.push(current_project_id: project.id) if project
end
def render_show def render_show
push_rule push_rule
......
= render 'shared/projects/protected_branches/update_protected_branch', protected_branch: protected_branch = render 'shared/projects/protected_branches/update_protected_branch', protected_branch: protected_branch
...@@ -9,6 +9,7 @@ RSpec.describe 'Protected Branches', :js do ...@@ -9,6 +9,7 @@ RSpec.describe 'Protected Branches', :js do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
before do before do
stub_feature_flags(deploy_keys_on_protected_branches: false)
sign_in(user) sign_in(user)
end end
...@@ -187,4 +188,14 @@ RSpec.describe 'Protected Branches', :js do ...@@ -187,4 +188,14 @@ RSpec.describe 'Protected Branches', :js do
end end
end end
end end
context 'when the users for protected branches feature is on' do
before do
stub_licensed_features(protected_refs_for_users: true)
end
include_examples 'when the deploy_keys_on_protected_branches FF is turned on' do
let(:all_dropdown_sections) { %w(Roles Users Deploy\ Keys) }
end
end
end end
...@@ -19,6 +19,7 @@ describe('EE ProtectedBranchEdit', () => { ...@@ -19,6 +19,7 @@ describe('EE ProtectedBranchEdit', () => {
</div>`); </div>`);
jest.spyOn(ProtectedBranchEdit.prototype, 'buildDropdowns').mockImplementation(); jest.spyOn(ProtectedBranchEdit.prototype, 'buildDropdowns').mockImplementation();
gon.features = { deployKeysOnProtectedBranches: false };
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
}); });
......
...@@ -1024,6 +1024,11 @@ msgid_plural "%d days" ...@@ -1024,6 +1024,11 @@ msgid_plural "%d days"
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
msgid "1 deploy key"
msgid_plural "%d deploy keys"
msgstr[0] ""
msgstr[1] ""
msgid "1 group" msgid "1 group"
msgid_plural "%d groups" msgid_plural "%d groups"
msgstr[0] "" msgstr[0] ""
...@@ -1324,6 +1329,9 @@ msgstr "" ...@@ -1324,6 +1329,9 @@ msgstr ""
msgid "Access to Pages websites are controlled based on the user's membership to a given project. By checking this box, users will be required to be logged in to have access to all Pages websites in your instance." msgid "Access to Pages websites are controlled based on the user's membership to a given project. By checking this box, users will be required to be logged in to have access to all Pages websites in your instance."
msgstr "" msgstr ""
msgid "AccessDropdown|Deploy Keys"
msgstr ""
msgid "AccessDropdown|Groups" msgid "AccessDropdown|Groups"
msgstr "" msgstr ""
...@@ -10644,6 +10652,9 @@ msgstr "" ...@@ -10644,6 +10652,9 @@ msgstr ""
msgid "Failed to load branches. Please try again." msgid "Failed to load branches. Please try again."
msgstr "" msgstr ""
msgid "Failed to load deploy keys."
msgstr ""
msgid "Failed to load emoji list." msgid "Failed to load emoji list."
msgstr "" msgstr ""
...@@ -10659,6 +10670,9 @@ msgstr "" ...@@ -10659,6 +10670,9 @@ msgstr ""
msgid "Failed to load groups & users." msgid "Failed to load groups & users."
msgstr "" msgstr ""
msgid "Failed to load groups, users and deploy keys."
msgstr ""
msgid "Failed to load labels. Please try again." msgid "Failed to load labels. Please try again."
msgstr "" msgstr ""
...@@ -17916,6 +17930,9 @@ msgstr "" ...@@ -17916,6 +17930,9 @@ msgstr ""
msgid "Overwrite diverged branches" msgid "Overwrite diverged branches"
msgstr "" msgstr ""
msgid "Owned by %{image_tag}"
msgstr ""
msgid "Owned by anyone" msgid "Owned by anyone"
msgstr "" msgstr ""
......
...@@ -9,6 +9,10 @@ RSpec.describe 'Protected Branches', :js do ...@@ -9,6 +9,10 @@ RSpec.describe 'Protected Branches', :js do
let(:admin) { create(:admin) } let(:admin) { create(:admin) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
before do
stub_feature_flags(deploy_keys_on_protected_branches: false)
end
context 'logged in as developer' do context 'logged in as developer' do
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -163,4 +167,14 @@ RSpec.describe 'Protected Branches', :js do ...@@ -163,4 +167,14 @@ RSpec.describe 'Protected Branches', :js do
include_examples "protected branches > access control > CE" include_examples "protected branches > access control > CE"
end end
end end
context 'when the users for protected branches feature is off' do
before do
stub_licensed_features(protected_refs_for_users: false)
end
include_examples 'when the deploy_keys_on_protected_branches FF is turned on' do
let(:all_dropdown_sections) { %w(Roles Deploy\ Keys) }
end
end
end end
...@@ -14,6 +14,7 @@ describe('AccessDropdown', () => { ...@@ -14,6 +14,7 @@ describe('AccessDropdown', () => {
`); `);
const $dropdown = $('#dummy-dropdown'); const $dropdown = $('#dummy-dropdown');
$dropdown.data('defaultLabel', defaultLabel); $dropdown.data('defaultLabel', defaultLabel);
gon.features = { deployKeysOnProtectedBranches: true };
const options = { const options = {
$dropdown, $dropdown,
accessLevelsData: { accessLevelsData: {
...@@ -37,6 +38,9 @@ describe('AccessDropdown', () => { ...@@ -37,6 +38,9 @@ describe('AccessDropdown', () => {
{ type: LEVEL_TYPES.GROUP }, { type: LEVEL_TYPES.GROUP },
{ type: LEVEL_TYPES.GROUP }, { type: LEVEL_TYPES.GROUP },
{ type: LEVEL_TYPES.GROUP }, { type: LEVEL_TYPES.GROUP },
{ type: LEVEL_TYPES.DEPLOY_KEY },
{ type: LEVEL_TYPES.DEPLOY_KEY },
{ type: LEVEL_TYPES.DEPLOY_KEY },
]; ];
beforeEach(() => { beforeEach(() => {
...@@ -49,7 +53,7 @@ describe('AccessDropdown', () => { ...@@ -49,7 +53,7 @@ describe('AccessDropdown', () => {
const label = dropdown.toggleLabel(); const label = dropdown.toggleLabel();
expect(label).toBe('1 role, 2 users, 3 groups'); expect(label).toBe('1 role, 2 users, 3 deploy keys, 3 groups');
expect($dropdownToggleText).not.toHaveClass('is-default'); expect($dropdownToggleText).not.toHaveClass('is-default');
}); });
...@@ -122,6 +126,21 @@ describe('AccessDropdown', () => { ...@@ -122,6 +126,21 @@ describe('AccessDropdown', () => {
expect($dropdownToggleText).not.toHaveClass('is-default'); expect($dropdownToggleText).not.toHaveClass('is-default');
}); });
}); });
describe('with users and deploy keys', () => {
beforeEach(() => {
const selectedTypes = [LEVEL_TYPES.DEPLOY_KEY, LEVEL_TYPES.USER];
dropdown.setSelectedItems(dummyItems.filter(item => selectedTypes.includes(item.type)));
$dropdownToggleText.addClass('is-default');
});
it('displays number of deploy keys', () => {
const label = dropdown.toggleLabel();
expect(label).toBe('2 users, 3 deploy keys');
expect($dropdownToggleText).not.toHaveClass('is-default');
});
});
}); });
describe('userRowHtml', () => { describe('userRowHtml', () => {
......
# frozen_string_literal: true
RSpec.shared_examples 'when the deploy_keys_on_protected_branches FF is turned on' do
before do
stub_feature_flags(deploy_keys_on_protected_branches: true)
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 push' main dropdown, with only one deploy key" do
visit project_protected_branches_path(project)
find(".js-allowed-to-push").click
wait_for_requests
within('.qa-allowed-to-push-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
it "shows all sections but not deploy keys in the 'Allowed to merge' main dropdown" do
visit project_protected_branches_path(project)
find(".js-allowed-to-merge").click
wait_for_requests
within('.qa-allowed-to-merge-dropdown') do
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
end
end
it "shows all sections in the 'Allowed to push' update dropdown" do
create(:protected_branch, :no_one_can_push, project: project, name: 'master')
visit project_protected_branches_path(project)
within(".js-protected-branch-edit-form") do
find(".js-allowed-to-push").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 push' dropdown" do
visit project_protected_branches_path(project)
find(".js-allowed-to-push").click
wait_for_requests
within('.qa-allowed-to-push-dropdown') do
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
end
end
it "just shows all sections but not deploy keys in the 'Allowed to push' update dropdown" do
create(:protected_branch, :no_one_can_push, project: project, name: 'master')
visit project_protected_branches_path(project)
within(".js-protected-branch-edit-form") do
find(".js-allowed-to-push").click
wait_for_requests
dropdown_headers = page.all('.dropdown-header').map(&:text)
expect(dropdown_headers).to contain_exactly(*dropdown_sections_minus_deploy_keys)
end
end
end
end
end
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