Skip to content
代码片段 群组 项目
未验证 提交 f308529e 编辑于 作者: Alex Buijs's avatar Alex Buijs 提交者: GitLab
浏览文件

Add manage roles link to roles dropdowm

Add footer to role select dropdown in members table and invite modal

Changelog: added
EE: true
上级 25b10842
No related branches found
No related tags found
无相关合并请求
显示
220 个添加25 个删除
...@@ -45,6 +45,8 @@ export default { ...@@ -45,6 +45,8 @@ export default {
GlSprintf, GlSprintf,
GlButton, GlButton,
ContentTransition, ContentTransition,
ManageRolesDropdownFooter: () =>
import('ee_component/members/components/action_dropdowns/manage_roles_dropdown_footer.vue'),
}, },
mixins: [Tracking.mixin()], mixins: [Tracking.mixin()],
inheritAttrs: false, inheritAttrs: false,
...@@ -332,7 +334,11 @@ export default { ...@@ -332,7 +334,11 @@ export default {
:items="accessLevelOptions.formatted" :items="accessLevelOptions.formatted"
:loading="isLoadingRoles" :loading="isLoadingRoles"
block block
/> >
<template #footer>
<manage-roles-dropdown-footer />
</template>
</gl-collapsible-listbox>
</gl-form-group> </gl-form-group>
<gl-form-group <gl-form-group
......
...@@ -28,6 +28,7 @@ export default (function initInviteMembersModal() { ...@@ -28,6 +28,7 @@ export default (function initInviteMembersModal() {
newUsersUrl: el.dataset.newUsersUrl, newUsersUrl: el.dataset.newUsersUrl,
isCurrentUserAdmin: parseBoolean(el.dataset.isCurrentUserAdmin), isCurrentUserAdmin: parseBoolean(el.dataset.isCurrentUserAdmin),
isEmailSignupEnabled: parseBoolean(el.dataset.isSignupEnabled), isEmailSignupEnabled: parseBoolean(el.dataset.isSignupEnabled),
manageMemberRolesPath: el.dataset.manageMemberRolesPath,
}, },
render: (createElement) => render: (createElement) =>
createElement(InviteMembersModal, { createElement(InviteMembersModal, {
......
...@@ -16,6 +16,8 @@ export default { ...@@ -16,6 +16,8 @@ export default {
LdapDropdownFooter: () => LdapDropdownFooter: () =>
import('ee_component/members/components/action_dropdowns/ldap_dropdown_footer.vue'), import('ee_component/members/components/action_dropdowns/ldap_dropdown_footer.vue'),
CustomPermissions: () => import('ee_component/members/components/table/custom_permissions.vue'), CustomPermissions: () => import('ee_component/members/components/table/custom_permissions.vue'),
ManageRolesDropdownFooter: () =>
import('ee_component/members/components/action_dropdowns/manage_roles_dropdown_footer.vue'),
}, },
inject: ['namespace', 'group'], inject: ['namespace', 'group'],
props: { props: {
...@@ -120,6 +122,7 @@ export default { ...@@ -120,6 +122,7 @@ export default {
v-if="permissions.canOverride && member.isOverridden" v-if="permissions.canOverride && member.isOverridden"
:member-id="member.id" :member-id="member.id"
/> />
<manage-roles-dropdown-footer />
</template> </template>
</gl-collapsible-listbox> </gl-collapsible-listbox>
......
...@@ -27,6 +27,7 @@ export const initMembersApp = (el, options) => { ...@@ -27,6 +27,7 @@ export const initMembersApp = (el, options) => {
exportCsvPath, exportCsvPath,
groupName, groupName,
groupPath, groupPath,
manageMemberRolesPath,
...vuexStoreAttributes ...vuexStoreAttributes
} = parseDataAttributes(el); } = parseDataAttributes(el);
...@@ -73,6 +74,7 @@ export const initMembersApp = (el, options) => { ...@@ -73,6 +74,7 @@ export const initMembersApp = (el, options) => {
canFilterByEnterprise, canFilterByEnterprise,
canExportMembers, canExportMembers,
exportCsvPath, exportCsvPath,
manageMemberRolesPath,
group: { group: {
name: groupName, name: groupName,
path: groupPath, path: groupPath,
......
# frozen_string_literal: true # frozen_string_literal: true
module Projects::ProjectMembersHelper module Projects::ProjectMembersHelper
def project_members_app_data_json(project, members:, invited:, access_requests:, include_relations:, search:) def project_members_app_data_json(...)
{ project_members_app_data(...).to_json
user: project_members_list_data(project, members, { param_name: :page, params: { search_groups: nil } }),
group: project_group_links_list_data(project, include_relations, search),
invite: project_members_list_data(project, invited.nil? ? [] : invited),
access_request: project_members_list_data(project, access_requests.nil? ? [] : access_requests),
source_id: project.id,
can_manage_members: Ability.allowed?(current_user, :admin_project_member, project),
can_manage_access_requests: Ability.allowed?(current_user, :admin_member_access_request, project),
group_name: project.group&.name,
group_path: project.group&.full_path
}.to_json
end end
def project_member_header_subtext(project) def project_member_header_subtext(project)
...@@ -28,6 +18,20 @@ def project_member_header_subtext(project) ...@@ -28,6 +18,20 @@ def project_member_header_subtext(project)
private private
def project_members_app_data(project, members:, invited:, access_requests:, include_relations:, search:)
{
user: project_members_list_data(project, members, { param_name: :page, params: { search_groups: nil } }),
group: project_group_links_list_data(project, include_relations, search),
invite: project_members_list_data(project, invited.nil? ? [] : invited),
access_request: project_members_list_data(project, access_requests.nil? ? [] : access_requests),
source_id: project.id,
can_manage_members: Ability.allowed?(current_user, :admin_project_member, project),
can_manage_access_requests: Ability.allowed?(current_user, :admin_member_access_request, project),
group_name: project.group&.name,
group_path: project.group&.full_path
}
end
def share_project_description(project) def share_project_description(project)
share_with_group = project.allowed_to_share_with_group? share_with_group = project.allowed_to_share_with_group?
share_with_members = !membership_locked? share_with_members = !membership_locked?
......
<script> <script>
import { GlListboxItem } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { mapActions } from 'vuex'; import { mapActions } from 'vuex';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
...@@ -7,7 +7,7 @@ import { s__ } from '~/locale'; ...@@ -7,7 +7,7 @@ import { s__ } from '~/locale';
export default { export default {
name: 'LdapDropdownFooter', name: 'LdapDropdownFooter',
components: { components: {
GlListboxItem, GlButton,
}, },
inject: ['namespace'], inject: ['namespace'],
props: { props: {
...@@ -36,9 +36,9 @@ export default { ...@@ -36,9 +36,9 @@ export default {
</script> </script>
<template> <template>
<ul class="gl-border-t-1 gl-border-t-solid gl-border-t-gray-200 gl-new-dropdown-contents"> <div class="gl-border-t gl-border-gray-200 gl-p-2">
<gl-listbox-item @select="handleClick"> <gl-button category="tertiary" class="gl-w-full" @click="handleClick">
{{ s__('Members|Revert to LDAP group sync settings') }} {{ s__('Members|Revert to LDAP synced settings') }}
</gl-listbox-item> </gl-button>
</ul> </div>
</template> </template>
<script>
import { GlButton } from '@gitlab/ui';
export default {
components: {
GlButton,
},
inject: {
manageMemberRolesPath: {
default: null,
},
},
};
</script>
<template>
<div v-if="manageMemberRolesPath" class="gl-border-t gl-border-gray-200 gl-p-2">
<gl-button
:href="manageMemberRolesPath"
category="tertiary"
class="gl-w-full gl-justify-content-start!"
>
{{ s__('MemberRole|Manage roles') }}
</gl-button>
</div>
</template>
...@@ -17,7 +17,8 @@ def group_members_app_data(group, members:, invited:, access_requests:, banned:, ...@@ -17,7 +17,8 @@ def group_members_app_data(group, members:, invited:, access_requests:, banned:,
can_export_members: can?(current_user, :export_group_memberships, group), can_export_members: can?(current_user, :export_group_memberships, group),
export_csv_path: export_csv_group_group_members_path(group), export_csv_path: export_csv_group_group_members_path(group),
can_filter_by_enterprise: group.domain_verification_available? && can?(current_user, :admin_group_member, group), can_filter_by_enterprise: group.domain_verification_available? && can?(current_user, :admin_group_member, group),
banned: group_members_list_data(group, banned) banned: group_members_list_data(group, banned),
manage_member_roles_path: manage_member_roles_path(group)
}) })
end end
......
...@@ -28,6 +28,8 @@ def common_invite_modal_dataset(source) ...@@ -28,6 +28,8 @@ def common_invite_modal_dataset(source)
) )
end end
dataset[:manage_member_roles_path] = manage_member_roles_path(source)
dataset dataset
end end
......
...@@ -3,6 +3,13 @@ ...@@ -3,6 +3,13 @@
module EE module EE
module Projects module Projects
module ProjectMembersHelper module ProjectMembersHelper
extend ::Gitlab::Utils::Override
override :project_members_app_data
def project_members_app_data(project, ...)
super.merge(manage_member_roles_path: manage_member_roles_path(project))
end
def project_member_header_subtext(project) def project_member_header_subtext(project)
if project.group && if project.group &&
::Namespaces::FreeUserCap::Enforcement.new(project.root_ancestor).enforce_cap? && ::Namespaces::FreeUserCap::Enforcement.new(project.root_ancestor).enforce_cap? &&
......
# frozen_string_literal: true
module MemberRolesHelper
def manage_member_roles_path(source)
root_group = source&.root_ancestor
return unless root_group&.custom_roles_enabled?
if ::Gitlab::Saas.feature_available?(:group_custom_roles) && can?(current_user, :admin_group_member, root_group)
group_settings_roles_and_permissions_path(root_group)
elsif current_user&.can_admin_all_resources?
admin_application_settings_roles_and_permissions_path
end
end
end
...@@ -81,7 +81,8 @@ ...@@ -81,7 +81,8 @@
expect(page).not_to have_selector user_action_dropdown expect(page).not_to have_selector user_action_dropdown
expect(page).to have_button 'Guest', disabled: false expect(page).to have_button 'Guest', disabled: false
select_from_listbox('Revert to LDAP group sync settings', from: 'Guest') click_button 'Guest'
click_button 'Revert to LDAP synced settings'
wait_for_requests wait_for_requests
...@@ -97,7 +98,7 @@ ...@@ -97,7 +98,7 @@
click_button 'Guest' click_button 'Guest'
expect(page).not_to have_content 'Revert to LDAP group sync settings' expect(page).not_to have_content 'Revert to LDAP synced settings'
end end
end end
end end
...@@ -7,6 +7,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; ...@@ -7,6 +7,7 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ContentTransition from '~/vue_shared/components/content_transition.vue'; import ContentTransition from '~/vue_shared/components/content_transition.vue';
import CEInviteModalBase from '~/invite_members/components/invite_modal_base.vue'; import CEInviteModalBase from '~/invite_members/components/invite_modal_base.vue';
import EEInviteModalBase from 'ee/invite_members/components/invite_modal_base.vue'; import EEInviteModalBase from 'ee/invite_members/components/invite_modal_base.vue';
import ManageRolesDropdownFooter from 'ee/members/components/action_dropdowns/manage_roles_dropdown_footer.vue';
import { import {
OVERAGE_MODAL_TITLE, OVERAGE_MODAL_TITLE,
OVERAGE_MODAL_CONTINUE_BUTTON, OVERAGE_MODAL_CONTINUE_BUTTON,
...@@ -413,4 +414,10 @@ describe('EEInviteModalBase', () => { ...@@ -413,4 +414,10 @@ describe('EEInviteModalBase', () => {
expect(findCEBase().props('invalidFeedbackMessage')).toBe('invalid message'); expect(findCEBase().props('invalidFeedbackMessage')).toBe('invalid message');
}); });
}); });
it('renders the ManageRolesDropdownFooter component', () => {
createComponent();
expect(wrapper.findComponent(ManageRolesDropdownFooter).exists()).toBe(true);
});
}); });
import { GlListboxItem } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
...@@ -51,7 +51,7 @@ describe('LdapDropdownFooter', () => { ...@@ -51,7 +51,7 @@ describe('LdapDropdownFooter', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
wrapper.findComponent(GlListboxItem).trigger('click'); wrapper.findComponent(GlButton).trigger('click');
}); });
it('calls `updateLdapOverride` action', () => { it('calls `updateLdapOverride` action', () => {
......
import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import ManageRolesDropdownFooter from 'ee/members/components/action_dropdowns/manage_roles_dropdown_footer.vue';
describe('ManageRolesDropdownFooter', () => {
let wrapper;
const manageMemberRolesPath = 'some path';
const createComponent = (provide = {}) => {
wrapper = shallowMount(ManageRolesDropdownFooter, {
provide: {
manageMemberRolesPath,
...provide,
},
});
};
const findButton = () => wrapper.findComponent(GlButton);
it('renders a button with the correct text and link', () => {
createComponent();
expect(findButton().exists()).toBe(true);
expect(findButton().text()).toBe('Manage roles');
expect(findButton().attributes('href')).toBe(manageMemberRolesPath);
});
it('renders no button when no `manageMemberRolesPath` is provided', () => {
createComponent({ manageMemberRolesPath: null });
expect(findButton().exists()).toBe(false);
});
});
...@@ -4,6 +4,7 @@ import Vue from 'vue'; ...@@ -4,6 +4,7 @@ import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex'; import Vuex from 'vuex';
import LdapDropdownFooter from 'ee/members/components/action_dropdowns/ldap_dropdown_footer.vue'; import LdapDropdownFooter from 'ee/members/components/action_dropdowns/ldap_dropdown_footer.vue';
import ManageRolesDropdownFooter from 'ee/members/components/action_dropdowns/manage_roles_dropdown_footer.vue';
import { guestOverageConfirmAction } from 'ee/members/guest_overage_confirm_action'; import { guestOverageConfirmAction } from 'ee/members/guest_overage_confirm_action';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import MaxRole from '~/members/components/table/max_role.vue'; import MaxRole from '~/members/components/table/max_role.vue';
...@@ -156,6 +157,12 @@ describe('MaxRole', () => { ...@@ -156,6 +157,12 @@ describe('MaxRole', () => {
}); });
}); });
it('renders the ManageRolesDropdownFooter component', () => {
createComponent();
expect(wrapper.findComponent(ManageRolesDropdownFooter).exists()).toBe(true);
});
describe('when member has custom roles', () => { describe('when member has custom roles', () => {
it('renders static and custom roles', () => { it('renders static and custom roles', () => {
createComponent(); createComponent();
......
...@@ -31,6 +31,7 @@ ...@@ -31,6 +31,7 @@
before do before do
allow(helper).to receive(:override_group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id/override') allow(helper).to receive(:override_group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id/override')
allow(helper).to receive(:group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id') allow(helper).to receive(:group_group_member_path).with(group, ':id').and_return('/groups/foo-bar/-/group_members/:id')
allow(helper).to receive(:manage_member_roles_path).with(group).and_return(admin_application_settings_roles_and_permissions_path)
allow(helper).to receive(:can?).with(current_user, :admin_group_member, group).and_return(true) allow(helper).to receive(:can?).with(current_user, :admin_group_member, group).and_return(true)
allow(helper).to receive(:can?).with(current_user, :admin_member_access_request, group).and_return(true) allow(helper).to receive(:can?).with(current_user, :admin_member_access_request, group).and_return(true)
allow(helper).to receive(:can?).with(current_user, :export_group_memberships, group).and_return(true) allow(helper).to receive(:can?).with(current_user, :export_group_memberships, group).and_return(true)
...@@ -48,6 +49,10 @@ ...@@ -48,6 +49,10 @@
expect(subject[:export_csv_path]).not_to be_nil expect(subject[:export_csv_path]).not_to be_nil
end end
it 'adds `manage_member_roles_path`' do
expect(subject[:manage_member_roles_path]).to eq(admin_application_settings_roles_and_permissions_path)
end
describe '`can_filter_by_enterprise`', :saas do describe '`can_filter_by_enterprise`', :saas do
where(:domain_verification_availabe_for_group, :can_admin_group_member, :expected_value) do where(:domain_verification_availabe_for_group, :can_admin_group_member, :expected_value) do
true | true | true true | true | true
......
...@@ -127,6 +127,18 @@ ...@@ -127,6 +127,18 @@
expect(helper.common_invite_modal_dataset(namespace)).not_to have_key(:active_trial_dataset) expect(helper.common_invite_modal_dataset(namespace)).not_to have_key(:active_trial_dataset)
end end
end end
describe 'including the manage_member_roles_path' do
before do
allow(helper).to receive(:manage_member_roles_path).with(project)
.and_return(admin_application_settings_roles_and_permissions_path)
end
it 'does not include users limit notification data' do
expect(helper.common_invite_modal_dataset(project))
.to include(manage_member_roles_path: admin_application_settings_roles_and_permissions_path)
end
end
end end
describe '#users_filter_data' do describe '#users_filter_data' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe MemberRolesHelper, feature_category: :permissions do
let_it_be(:user) { build_stubbed(:user) }
let_it_be(:source) { build_stubbed(:group) }
let_it_be(:root_group) { source.root_ancestor }
before do
stub_licensed_features(custom_roles: true)
allow(helper).to receive(:current_user).and_return(user)
end
describe '#manage_member_roles_path' do
subject { helper.manage_member_roles_path(source) }
context 'when on saas', :saas do
it { is_expected.to be_nil }
context 'as owner' do
before do
allow(helper).to receive(:can?).with(user, :admin_group_member, root_group).and_return(true)
end
it { is_expected.to eq(group_settings_roles_and_permissions_path(root_group)) }
context 'when custom roles are not available' do
before do
stub_licensed_features(custom_roles: false)
end
it { is_expected.to be_nil }
end
end
end
context 'when in admin mode', :enable_admin_mode do
it { is_expected.to be_nil }
context 'as admin' do
let_it_be(:user) { build_stubbed(:user, :admin) }
it { is_expected.to eq(admin_application_settings_roles_and_permissions_path) }
context 'when custom roles are not available' do
before do
stub_licensed_features(custom_roles: false)
end
it { is_expected.to be_nil }
end
end
end
end
end
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
create_schedule_with_user(project, current_user) create_schedule_with_user(project, current_user)
allow(helper).to receive(:can_admin_project_member?).and_return(true) allow(helper).to receive(:can_admin_project_member?).and_return(true)
allow(helper).to receive(:can?).and_return(true) allow(helper).to receive(:can?).and_return(true)
allow(helper).to receive(:manage_member_roles_path).with(project)
.and_return(admin_application_settings_roles_and_permissions_path)
end end
it 'does not execute N+1' do it 'does not execute N+1' do
...@@ -37,6 +39,11 @@ ...@@ -37,6 +39,11 @@
expect { call_project_members_app_data_json }.not_to exceed_query_limit(control).with_threshold(11) # existing n+1 expect { call_project_members_app_data_json }.not_to exceed_query_limit(control).with_threshold(11) # existing n+1
end end
it 'includes `manage_member_roles_path` data' do
expect(Gitlab::Json.parse(call_project_members_app_data_json))
.to include('manage_member_roles_path' => admin_application_settings_roles_and_permissions_path)
end
def call_project_members_app_data_json def call_project_members_app_data_json
helper.project_members_app_data_json( helper.project_members_app_data_json(
project, project,
......
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册