diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-02 15:10:59 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-12-02 15:10:59 +0300 |
commit | f78aa88c769acebd95eca52b07169a57196a3318 (patch) | |
tree | fa4e1ce197ced08f86066e2d8d98e9d7d66a47d1 | |
parent | cbd97a2467d53b89fe4896b61ed5ab3f7203f111 (diff) |
Add latest changes from gitlab-org/gitlab@master
18 files changed, 169 insertions, 217 deletions
diff --git a/.rubocop_todo/layout/line_continuation_spacing.yml b/.rubocop_todo/layout/line_continuation_spacing.yml index c406fd1cae9..46e997246ea 100644 --- a/.rubocop_todo/layout/line_continuation_spacing.yml +++ b/.rubocop_todo/layout/line_continuation_spacing.yml @@ -107,7 +107,6 @@ Layout/LineContinuationSpacing: - 'lib/api/metrics/dashboard/annotations.rb' - 'lib/gitlab/auth/user_access_denied_reason.rb' - 'lib/gitlab/background_migration/populate_operation_visibility_permissions_from_operations.rb' - - 'lib/gitlab/checks/tag_check.rb' - 'lib/gitlab/ci/parsers/security/validators/schema_validator.rb' - 'lib/gitlab/database/background_migration/batched_migration_runner.rb' - 'lib/gitlab/database/load_balancing/sidekiq_server_middleware.rb' diff --git a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml index e38219963fd..68d0f57006e 100644 --- a/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml +++ b/.rubocop_todo/layout/line_end_string_concatenation_indentation.yml @@ -155,7 +155,6 @@ Layout/LineEndStringConcatenationIndentation: - 'lib/gitlab/auth.rb' - 'lib/gitlab/background_migration/populate_operation_visibility_permissions_from_operations.rb' - 'lib/gitlab/changelog/config.rb' - - 'lib/gitlab/checks/tag_check.rb' - 'lib/gitlab/ci/parsers/security/validators/schema_validator.rb' - 'lib/gitlab/ci/pipeline/chain/populate.rb' - 'lib/gitlab/ci/pipeline/seed/build.rb' diff --git a/.rubocop_todo/style/if_unless_modifier.yml b/.rubocop_todo/style/if_unless_modifier.yml index bf767c1701e..6c52c2d9f8e 100644 --- a/.rubocop_todo/style/if_unless_modifier.yml +++ b/.rubocop_todo/style/if_unless_modifier.yml @@ -730,7 +730,6 @@ Style/IfUnlessModifier: - 'lib/gitlab/checks/matching_merge_request.rb' - 'lib/gitlab/checks/push_check.rb' - 'lib/gitlab/checks/push_file_count_check.rb' - - 'lib/gitlab/checks/tag_check.rb' - 'lib/gitlab/ci/ansi2html.rb' - 'lib/gitlab/ci/ansi2json/converter.rb' - 'lib/gitlab/ci/ansi2json/style.rb' diff --git a/.rubocop_todo/style/inline_disable_annotation.yml b/.rubocop_todo/style/inline_disable_annotation.yml index 6140c5227a6..54203bada3d 100644 --- a/.rubocop_todo/style/inline_disable_annotation.yml +++ b/.rubocop_todo/style/inline_disable_annotation.yml @@ -2410,7 +2410,6 @@ Style/InlineDisableAnnotation: - 'lib/gitlab/checks/branch_check.rb' - 'lib/gitlab/checks/diff_check.rb' - 'lib/gitlab/checks/matching_merge_request.rb' - - 'lib/gitlab/checks/tag_check.rb' - 'lib/gitlab/ci/ansi2html.rb' - 'lib/gitlab/ci/ansi2json/parser.rb' - 'lib/gitlab/ci/badge/pipeline/status.rb' diff --git a/app/assets/javascripts/members/components/table/role_dropdown.vue b/app/assets/javascripts/members/components/table/max_role.vue index 2b72a3fe6e8..d0b04752ef2 100644 --- a/app/assets/javascripts/members/components/table/role_dropdown.vue +++ b/app/assets/javascripts/members/components/table/max_role.vue @@ -1,5 +1,5 @@ <script> -import { GlCollapsibleListbox } from '@gitlab/ui'; +import { GlBadge, GlCollapsibleListbox } from '@gitlab/ui'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; // eslint-disable-next-line no-restricted-imports import { mapActions } from 'vuex'; @@ -9,11 +9,12 @@ import { roleDropdownItems, initialSelectedRole } from 'ee_else_ce/members/utils import { s__ } from '~/locale'; export default { - name: 'RoleDropdown', components: { GlCollapsibleListbox, + GlBadge, LdapDropdownFooter: () => import('ee_component/members/components/action_dropdowns/ldap_dropdown_footer.vue'), + CustomPermissions: () => import('ee_component/members/components/table/custom_permissions.vue'), }, inject: ['namespace', 'group'], props: { @@ -27,37 +28,40 @@ export default { }, }, data() { + const accessLevelOptions = roleDropdownItems(this.member); return { - isDesktop: false, + accessLevelOptions, busy: false, - selectedRole: null, + customPermissions: this.member.customPermissions ?? [], + isDesktop: false, + memberRoleId: this.member.accessLevel.memberRoleId ?? null, + selectedRole: initialSelectedRole(accessLevelOptions.flatten, this.member), }; }, computed: { disabled() { return this.permissions.canOverride && !this.member.isOverridden; }, - dropdownItems() { - return roleDropdownItems(this.member); - }, - }, - created() { - this.selectedRole = initialSelectedRole(this.dropdownItems.flatten, this.member); }, mounted() { this.isDesktop = bp.isDesktop(); }, methods: { ...mapActions({ - updateMemberRole(dispatch, payload) { - return dispatch(`${this.namespace}/updateMemberRole`, payload); + updateMemberRole(dispatch, { memberId, accessLevel, memberRoleId }) { + return dispatch(`${this.namespace}/updateMemberRole`, { + memberId, + accessLevel, + memberRoleId, + }); }, }), async handleSelect(value) { this.busy = true; - const newRole = this.dropdownItems.flatten.find((item) => item.value === value); + const newRole = this.accessLevelOptions.flatten.find((item) => item.value === value); const previousRole = this.selectedRole; + const previousMemberRoleId = this.memberRoleId; try { const confirmed = await guestOverageConfirmAction({ @@ -74,18 +78,18 @@ export default { } this.selectedRole = value; + this.memberRoleId = newRole.memberRoleId; await this.updateMemberRole({ memberId: this.member.id, - accessLevel: { - integerValue: newRole.accessLevel, - memberRoleId: newRole.memberRoleId, - }, + accessLevel: newRole.accessLevel, + memberRoleId: newRole.memberRoleId, }); this.$toast.show(s__('Members|Role updated successfully.')); } catch (error) { this.selectedRole = previousRole; + this.memberRoleId = previousMemberRoleId; Sentry.captureException(error); } finally { this.busy = false; @@ -96,25 +100,35 @@ export default { </script> <template> - <gl-collapsible-listbox - :placement="isDesktop ? 'left' : 'right'" - :toggle-text="member.accessLevel.stringValue" - :header-text="__('Change role')" - :disabled="disabled" - :loading="busy" - data-qa-selector="access_level_dropdown" - :items="dropdownItems.formatted" - :selected="selectedRole" - @select="handleSelect" - > - <template #list-item="{ item }"> - <span data-qa-selector="access_level_link">{{ item.text }}</span> - </template> - <template #footer> - <ldap-dropdown-footer - v-if="permissions.canOverride && member.isOverridden" - :member-id="member.id" - /> - </template> - </gl-collapsible-listbox> + <div> + <gl-collapsible-listbox + v-if="permissions.canUpdate" + :placement="isDesktop ? 'left' : 'right'" + :header-text="__('Change role')" + :disabled="disabled" + :loading="busy" + data-qa-selector="access_level_dropdown" + :items="accessLevelOptions.formatted" + :selected="selectedRole" + @select="handleSelect" + > + <template #list-item="{ item }"> + <span data-qa-selector="access_level_link">{{ item.text }}</span> + </template> + <template #footer> + <ldap-dropdown-footer + v-if="permissions.canOverride && member.isOverridden" + :member-id="member.id" + /> + </template> + </gl-collapsible-listbox> + + <gl-badge v-else>{{ member.accessLevel.stringValue }}</gl-badge> + + <custom-permissions + v-if="memberRoleId !== null" + :member-role-id="memberRoleId" + :custom-permissions="customPermissions" + /> + </div> </template> diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue index 2b3294c1c79..149947397ad 100644 --- a/app/assets/javascripts/members/components/table/members_table.vue +++ b/app/assets/javascripts/members/components/table/members_table.vue @@ -31,7 +31,7 @@ import MemberActions from './member_actions.vue'; import MemberAvatar from './member_avatar.vue'; import MemberSource from './member_source.vue'; import MemberActivity from './member_activity.vue'; -import RoleDropdown from './role_dropdown.vue'; +import MaxRole from './max_role.vue'; export default { name: 'MembersTable', @@ -44,7 +44,7 @@ export default { MembersTableCell, MemberSource, MemberActions, - RoleDropdown, + MaxRole, RemoveGroupLinkModal, RemoveMemberModal, ExpirationDatepicker, @@ -292,8 +292,7 @@ export default { <template #cell(maxRole)="{ item: member }"> <members-table-cell #default="{ permissions }" :member="member"> - <role-dropdown v-if="permissions.canUpdate" :permissions="permissions" :member="member" /> - <gl-badge v-else>{{ member.accessLevel.stringValue }}</gl-badge> + <max-role :permissions="permissions" :member="member" /> </members-table-cell> </template> diff --git a/app/assets/javascripts/members/index.js b/app/assets/javascripts/members/index.js index 87ae670c146..ad477d8b4b6 100644 --- a/app/assets/javascripts/members/index.js +++ b/app/assets/javascripts/members/index.js @@ -2,6 +2,8 @@ import { GlToast } from '@gitlab/ui'; import Vue from 'vue'; // eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; import { parseDataAttributes } from '~/members/utils'; import { MEMBER_TYPES } from 'ee_else_ce/members/constants'; import MembersTabs from './components/members_tabs.vue'; @@ -13,6 +15,7 @@ export const initMembersApp = (el, options) => { } Vue.use(Vuex); + Vue.use(VueApollo); Vue.use(GlToast); const { @@ -61,6 +64,7 @@ export const initMembersApp = (el, options) => { el, components: { MembersTabs }, store, + apolloProvider: new VueApollo({ defaultClient: createDefaultClient() }), provide: { currentUserId: gon.current_user_id || null, sourceId, diff --git a/app/assets/javascripts/members/store/actions.js b/app/assets/javascripts/members/store/actions.js index d696f618a3c..54a53e7c0a9 100644 --- a/app/assets/javascripts/members/store/actions.js +++ b/app/assets/javascripts/members/store/actions.js @@ -2,17 +2,15 @@ import axios from '~/lib/utils/axios_utils'; import { formatDate } from '~/lib/utils/datetime_utility'; import * as types from './mutation_types'; -export const updateMemberRole = async ({ state, commit }, { memberId, accessLevel }) => { +export const updateMemberRole = async ( + { state, commit }, + { memberId, accessLevel, memberRoleId }, +) => { try { await axios.put( state.memberPath.replace(/:id$/, memberId), - state.requestFormatter({ - accessLevel: accessLevel.integerValue, - memberRoleId: accessLevel.memberRoleId, - }), + state.requestFormatter({ accessLevel, memberRoleId }), ); - - commit(types.RECEIVE_MEMBER_ROLE_SUCCESS, { memberId, accessLevel }); } catch (error) { commit(types.RECEIVE_MEMBER_ROLE_ERROR, { error }); diff --git a/app/assets/javascripts/members/store/mutation_types.js b/app/assets/javascripts/members/store/mutation_types.js index 5fa75725552..c1cdbf6146f 100644 --- a/app/assets/javascripts/members/store/mutation_types.js +++ b/app/assets/javascripts/members/store/mutation_types.js @@ -1,4 +1,3 @@ -export const RECEIVE_MEMBER_ROLE_SUCCESS = 'RECEIVE_MEMBER_ROLE_SUCCESS'; export const RECEIVE_MEMBER_ROLE_ERROR = 'RECEIVE_MEMBER_ROLE_ERROR'; export const RECEIVE_MEMBER_EXPIRATION_SUCCESS = 'RECEIVE_MEMBER_EXPIRATION_SUCCESS'; diff --git a/app/assets/javascripts/members/store/mutations.js b/app/assets/javascripts/members/store/mutations.js index b4cf9f3480f..edc400aef7d 100644 --- a/app/assets/javascripts/members/store/mutations.js +++ b/app/assets/javascripts/members/store/mutations.js @@ -4,15 +4,6 @@ import * as types from './mutation_types'; import { findMember } from './utils'; export default { - [types.RECEIVE_MEMBER_ROLE_SUCCESS](state, { memberId, accessLevel }) { - const member = findMember(state, memberId); - - if (!member) { - return; - } - - Vue.set(member, 'accessLevel', accessLevel); - }, [types.RECEIVE_MEMBER_ROLE_ERROR](state, { error }) { state.errorMessage = error.response?.data?.message || diff --git a/lib/gitlab/checks/tag_check.rb b/lib/gitlab/checks/tag_check.rb index d5addab74b8..5684b897256 100644 --- a/lib/gitlab/checks/tag_check.rb +++ b/lib/gitlab/checks/tag_check.rb @@ -6,8 +6,8 @@ module Gitlab ERROR_MESSAGES = { change_existing_tags: 'You are not allowed to change existing tags on this project.', update_protected_tag: 'Protected tags cannot be updated.', - delete_protected_tag: 'You are not allowed to delete protected tags from this project. '\ - 'Only a project maintainer or owner can delete a protected tag.', + delete_protected_tag: 'You are not allowed to delete protected tags from this project. ' \ + 'Only a project maintainer or owner can delete a protected tag.', delete_protected_tag_non_web: 'You can only delete protected tags using the web interface.', create_protected_tag: 'You are not allowed to create this tag as it is protected.', default_branch_collision: 'You cannot use default branch name to create a tag', @@ -24,69 +24,85 @@ module Gitlab def validate! return unless tag_name - logger.log_timed(LOG_MESSAGES[:tag_checks]) do - if tag_exists? && user_access.cannot_do_action?(:admin_tag) - raise GitAccess::ForbiddenError, ERROR_MESSAGES[:change_existing_tags] - end - end - - default_branch_collision_check + logger.log_timed(LOG_MESSAGES[:tag_checks]) { tag_checks } + logger.log_timed(LOG_MESSAGES[:default_branch_collision_check]) { default_branch_collision_check } prohibited_tag_checks - protected_tag_checks + logger.log_timed(LOG_MESSAGES[:protected_tag_checks]) { protected_tag_checks } end private + def tag_checks + return unless tag_exists? && user_access.cannot_do_action?(:admin_tag) + + raise GitAccess::ForbiddenError, ERROR_MESSAGES[:change_existing_tags] + end + + def default_branch_collision_check + return unless creation? && tag_name == project.default_branch + + raise GitAccess::ForbiddenError, ERROR_MESSAGES[:default_branch_collision] + end + def prohibited_tag_checks return if deletion? - unless Gitlab::GitRefValidator.validate(tag_name) - raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_tag_name] - end + # Incorrectly encoded tags names may raise during other checks so we + # need to validate the encoding first + validate_encoding! + validate_valid_tag_name! + validate_tag_name_not_fully_qualified! + end - if tag_name.start_with?("refs/tags/") # rubocop: disable Style/GuardClause - raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_tag_name] - end + def protected_tag_checks + return unless ProtectedTag.protected?(project, tag_name) - # rubocop: disable Style/GuardClause - # rubocop: disable Style/SoleNestedConditional - if Feature.enabled?(:prohibited_tag_name_encoding_check, project) - unless Gitlab::EncodingHelper.force_encode_utf8(tag_name).valid_encoding? - raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_tag_name_encoding] - end - end - # rubocop: enable Style/SoleNestedConditional - # rubocop: enable Style/GuardClause + validate_protected_tag_update! + validate_protected_tag_deletion! + validate_protected_tag_creation! end - def protected_tag_checks - logger.log_timed(LOG_MESSAGES[__method__]) do - return unless ProtectedTag.protected?(project, tag_name) # rubocop:disable Cop/AvoidReturnFromBlocks + def validate_encoding! + return unless Feature.enabled?(:prohibited_tag_name_encoding_check, project) + return if Gitlab::EncodingHelper.force_encode_utf8(tag_name).valid_encoding? - raise(GitAccess::ForbiddenError, ERROR_MESSAGES[:update_protected_tag]) if update? + raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_tag_name_encoding] + end + + def validate_valid_tag_name! + return if Gitlab::GitRefValidator.validate(tag_name) - if deletion? - unless user_access.user.can?(:maintainer_access, project) - raise(GitAccess::ForbiddenError, ERROR_MESSAGES[:delete_protected_tag]) - end + raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_tag_name] + end - unless updated_from_web? - raise GitAccess::ForbiddenError, ERROR_MESSAGES[:delete_protected_tag_non_web] - end - end + def validate_tag_name_not_fully_qualified! + return unless tag_name.start_with?("refs/tags/") - unless user_access.can_create_tag?(tag_name) - raise GitAccess::ForbiddenError, ERROR_MESSAGES[:create_protected_tag] - end - end + raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_tag_name] end - def default_branch_collision_check - logger.log_timed(LOG_MESSAGES[:default_branch_collision_check]) do - if creation? && tag_name == project.default_branch - raise GitAccess::ForbiddenError, ERROR_MESSAGES[:default_branch_collision] - end + def validate_protected_tag_update! + return unless update? + + raise(GitAccess::ForbiddenError, ERROR_MESSAGES[:update_protected_tag]) + end + + def validate_protected_tag_deletion! + return unless deletion? + + unless user_access.user.can?(:maintainer_access, project) + raise(GitAccess::ForbiddenError, ERROR_MESSAGES[:delete_protected_tag]) end + + return if updated_from_web? + + raise GitAccess::ForbiddenError, ERROR_MESSAGES[:delete_protected_tag_non_web] + end + + def validate_protected_tag_creation! + return if user_access.can_create_tag?(tag_name) + + raise GitAccess::ForbiddenError, ERROR_MESSAGES[:create_protected_tag] end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 71dfa498a66..2ebe97ba74b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -29632,6 +29632,9 @@ msgstr "" msgid "MemberRole|%{requirement} has to be enabled in order to enable %{permission}." msgstr "" +msgid "MemberRole|Custom permissions:" +msgstr "" + msgid "MemberRole|can't be changed" msgstr "" diff --git a/qa/qa/page/component/members/members_table.rb b/qa/qa/page/component/members/members_table.rb index 8bc20da83af..655a28f947e 100644 --- a/qa/qa/page/component/members/members_table.rb +++ b/qa/qa/page/component/members/members_table.rb @@ -21,7 +21,7 @@ module QA element :member_row end - base.view 'app/assets/javascripts/members/components/table/role_dropdown.vue' do + base.view 'app/assets/javascripts/members/components/table/max_role.vue' do element :access_level_dropdown element :access_level_link end diff --git a/spec/frontend/members/components/table/role_dropdown_spec.js b/spec/frontend/members/components/table/max_role_spec.js index 62275a05dc5..75e1e05afb1 100644 --- a/spec/frontend/members/components/table/role_dropdown_spec.js +++ b/spec/frontend/members/components/table/max_role_spec.js @@ -1,4 +1,4 @@ -import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui'; +import { GlBadge, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { mount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; @@ -6,16 +6,19 @@ import Vue, { nextTick } from 'vue'; import Vuex from 'vuex'; import * as Sentry from '~/sentry/sentry_browser_wrapper'; import waitForPromises from 'helpers/wait_for_promises'; -import RoleDropdown from '~/members/components/table/role_dropdown.vue'; +import MaxRole from '~/members/components/table/max_role.vue'; import { MEMBER_TYPES } from '~/members/constants'; import { guestOverageConfirmAction } from 'ee_else_ce/members/guest_overage_confirm_action'; import { member } from '../../mock_data'; Vue.use(Vuex); + jest.mock('ee_else_ce/members/guest_overage_confirm_action'); jest.mock('~/sentry/sentry_browser_wrapper'); -describe('RoleDropdown', () => { +guestOverageConfirmAction.mockReturnValue(true); + +describe('MaxRole', () => { let wrapper; let actions; const $toast = { @@ -35,7 +38,7 @@ describe('RoleDropdown', () => { }; const createComponent = (propsData = {}, store = createStore()) => { - wrapper = mount(RoleDropdown, { + wrapper = mount(MaxRole, { provide: { namespace: MEMBER_TYPES.user, group: { @@ -45,7 +48,9 @@ describe('RoleDropdown', () => { }, propsData: { member, - permissions: {}, + permissions: { + canUpdate: true, + }, ...propsData, }, store, @@ -55,6 +60,7 @@ describe('RoleDropdown', () => { }); }; + const findBadge = () => wrapper.findComponent(GlBadge); const findListbox = () => wrapper.findComponent(GlCollapsibleListbox); const findListboxItems = () => wrapper.findAllComponents(GlListboxItem); const findListboxItemByText = (text) => @@ -64,6 +70,18 @@ describe('RoleDropdown', () => { gon.features = { showOverageOnRolePromotion: true }; }); + describe('when member can not be updated', () => { + it('renders a badge instead of a collapsible listbox', () => { + createComponent({ + permissions: { + canUpdate: false, + }, + }); + + expect(findBadge().text()).toBe('Owner'); + }); + }); + it('has correct header text props', () => { createComponent(); expect(findListbox().props('headerText')).toBe('Change role'); @@ -77,14 +95,12 @@ describe('RoleDropdown', () => { describe('when listbox is open', () => { beforeEach(async () => { - guestOverageConfirmAction.mockReturnValue(true); createComponent(); await findListbox().vm.$emit('click'); }); it('sets dropdown toggle and checks selected role', () => { - expect(findListbox().props('toggleText')).toBe('Owner'); expect(findListbox().find('[aria-selected=true]').text()).toBe('Owner'); }); @@ -100,7 +116,8 @@ describe('RoleDropdown', () => { expect(actions.updateMemberRole).toHaveBeenCalledWith(expect.any(Object), { memberId: member.id, - accessLevel: { integerValue: 30, memberRoleId: null }, + accessLevel: 30, + memberRoleId: null, }); }); @@ -108,7 +125,7 @@ describe('RoleDropdown', () => { it('displays toast', async () => { await findListboxItemByText('Developer').trigger('click'); - await nextTick(); + await waitForPromises(); expect($toast.show).toHaveBeenCalledWith('Role updated successfully.'); }); @@ -146,7 +163,7 @@ describe('RoleDropdown', () => { it('does not display toast', async () => { await findListboxItemByText('Developer').trigger('click'); - await nextTick(); + await waitForPromises(); expect($toast.show).not.toHaveBeenCalled(); }); @@ -176,12 +193,6 @@ describe('RoleDropdown', () => { }); }); - it("sets initial dropdown toggle value to member's role", () => { - createComponent(); - - expect(findListbox().props('toggleText')).toBe('Owner'); - }); - it('sets the dropdown alignment to right on mobile', async () => { jest.spyOn(bp, 'isDesktop').mockReturnValue(false); createComponent(); @@ -199,54 +210,4 @@ describe('RoleDropdown', () => { expect(findListbox().props('placement')).toBe('left'); }); - - describe('guestOverageConfirmAction', () => { - const mockConfirmAction = ({ confirmed }) => { - guestOverageConfirmAction.mockResolvedValueOnce(confirmed); - }; - - beforeEach(() => { - createComponent(); - - findListbox().vm.$emit('click'); - }); - - afterEach(() => { - guestOverageConfirmAction.mockReset(); - }); - - describe('when guestOverageConfirmAction returns true', () => { - beforeEach(() => { - mockConfirmAction({ confirmed: true }); - - findListboxItemByText('Reporter').trigger('click'); - }); - - it('calls updateMemberRole', () => { - expect(actions.updateMemberRole).toHaveBeenCalled(); - }); - }); - - describe('when guestOverageConfirmAction returns false', () => { - beforeEach(() => { - mockConfirmAction({ confirmed: false }); - - findListboxItemByText('Reporter').trigger('click'); - }); - - it('does not call updateMemberRole', () => { - expect(actions.updateMemberRole).not.toHaveBeenCalled(); - }); - - it('re-enables dropdown', async () => { - await waitForPromises(); - - expect(findListbox().props('disabled')).toBe(false); - }); - - it('resets selected dropdown item', () => { - expect(findListbox().props('selected')).toMatch(/role-static-\d+/); - }); - }); - }); }); diff --git a/spec/frontend/members/components/table/members_table_spec.js b/spec/frontend/members/components/table/members_table_spec.js index 791155fcd1b..c2400fbc142 100644 --- a/spec/frontend/members/components/table/members_table_spec.js +++ b/spec/frontend/members/components/table/members_table_spec.js @@ -1,4 +1,4 @@ -import { GlBadge, GlPagination, GlTable } from '@gitlab/ui'; +import { GlPagination, GlTable } from '@gitlab/ui'; import Vue from 'vue'; // eslint-disable-next-line no-restricted-imports import Vuex from 'vuex'; @@ -11,7 +11,7 @@ import MemberAvatar from '~/members/components/table/member_avatar.vue'; import MemberSource from '~/members/components/table/member_source.vue'; import MemberActivity from '~/members/components/table/member_activity.vue'; import MembersTable from '~/members/components/table/members_table.vue'; -import RoleDropdown from '~/members/components/table/role_dropdown.vue'; +import MaxRole from '~/members/components/table/max_role.vue'; import { MEMBER_TYPES, MEMBER_STATE_CREATED, @@ -74,7 +74,7 @@ describe('MembersTable', () => { 'member-source', 'created-at', 'member-actions', - 'role-dropdown', + 'max-role', 'remove-group-link-modal', 'remove-member-modal', 'expiration-datepicker', @@ -110,7 +110,7 @@ describe('MembersTable', () => { ${'source'} | ${'Source'} | ${memberMock} | ${MemberSource} ${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt} ${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt} - ${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown} + ${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${MaxRole} ${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker} ${'activity'} | ${'Activity'} | ${memberMock} | ${MemberActivity} `('renders the $label field', ({ field, label, member, expectedComponent }) => { @@ -274,16 +274,6 @@ describe('MembersTable', () => { }); }); - describe('when member can not be updated', () => { - it('renders badge in "Max role" field', () => { - createComponent({ members: [memberMock], tableFields: ['maxRole'] }); - - expect( - wrapper.find(`[data-label="Max role"][role="cell"]`).findComponent(GlBadge).text(), - ).toBe(memberMock.accessLevel.stringValue); - }); - }); - it('adds QA selector to table', () => { createComponent(); diff --git a/spec/frontend/members/mock_data.js b/spec/frontend/members/mock_data.js index e0dc765b9e4..f550039bfdc 100644 --- a/spec/frontend/members/mock_data.js +++ b/spec/frontend/members/mock_data.js @@ -51,6 +51,7 @@ export const member = { 'Minimal access': 5, }, customRoles: [], + customPermissions: [], }; export const group = { diff --git a/spec/frontend/members/store/actions_spec.js b/spec/frontend/members/store/actions_spec.js index 1e7f09fe62b..30ea83abf22 100644 --- a/spec/frontend/members/store/actions_spec.js +++ b/spec/frontend/members/store/actions_spec.js @@ -37,28 +37,21 @@ describe('Vuex members actions', () => { describe('updateMemberRole', () => { const memberId = members[0].id; - const accessLevel = { integerValue: 30, memberRoleId: 90 }; + const accessLevel = 30; + const memberRoleId = 90; - const payload = { - memberId, - accessLevel, - }; + const payload = { memberId, accessLevel, memberRoleId }; describe('successful request', () => { - it(`commits ${types.RECEIVE_MEMBER_ROLE_SUCCESS} mutation`, async () => { + it(`updates member role`, async () => { mock.onPut().replyOnce(HTTP_STATUS_OK); - await testAction(updateMemberRole, payload, state, [ - { - type: types.RECEIVE_MEMBER_ROLE_SUCCESS, - payload, - }, - ]); + await testAction(updateMemberRole, payload, state, []); expect(mock.history.put[0].url).toBe('/groups/foo-bar/-/group_members/238'); expect(mockedRequestFormatter).toHaveBeenCalledWith({ - accessLevel: accessLevel.integerValue, - memberRoleId: accessLevel.memberRoleId, + accessLevel, + memberRoleId, }); }); }); diff --git a/spec/frontend/members/store/mutations_spec.js b/spec/frontend/members/store/mutations_spec.js index 8160cc373d8..240a14b2836 100644 --- a/spec/frontend/members/store/mutations_spec.js +++ b/spec/frontend/members/store/mutations_spec.js @@ -14,19 +14,6 @@ describe('Vuex members mutations', () => { }; }); - describe(types.RECEIVE_MEMBER_ROLE_SUCCESS, () => { - it('updates member', () => { - const accessLevel = { integerValue: 30, stringValue: 'Developer' }; - - mutations[types.RECEIVE_MEMBER_ROLE_SUCCESS](state, { - memberId: members[0].id, - accessLevel, - }); - - expect(state.members[0].accessLevel).toEqual(accessLevel); - }); - }); - describe(types.RECEIVE_MEMBER_ROLE_ERROR, () => { describe('when error does not have a message', () => { it('shows default error message', () => { |