diff --git a/config/application.rb b/config/application.rb index a1ed5fbd58aafae442593f41c30ad0ee7d69a33b..a9008e387cfa65b8ad319301272c4e444134730c 100644 --- a/config/application.rb +++ b/config/application.rb @@ -268,6 +268,7 @@ class Application < Rails::Application config.assets.precompile << "page_bundles/branches.css" config.assets.precompile << "page_bundles/build.css" config.assets.precompile << "page_bundles/ci_status.css" + config.assets.precompile << "page_bundles/ci_cd_settings.css" config.assets.precompile << "page_bundles/cluster_agents.css" config.assets.precompile << "page_bundles/clusters.css" config.assets.precompile << "page_bundles/cycle_analytics.css" diff --git a/ee/app/assets/javascripts/protected_environments/add_approvers.vue b/ee/app/assets/javascripts/protected_environments/add_approvers.vue new file mode 100644 index 0000000000000000000000000000000000000000..7f972fc7f22f91f2d7f99ec5ef3e76abc8237654 --- /dev/null +++ b/ee/app/assets/javascripts/protected_environments/add_approvers.vue @@ -0,0 +1,206 @@ +<script> +import { GlFormGroup, GlCollapse, GlAvatar, GlLink, GlFormInput } from '@gitlab/ui'; +import * as Sentry from '@sentry/browser'; +import { uniqueId } from 'lodash'; +import Api from 'ee/api'; +import { getUser } from '~/rest_api'; +import { s__ } from '~/locale'; +import AccessDropdown from '~/projects/settings/components/access_dropdown.vue'; +import { ACCESS_LEVELS } from './constants'; + +const mapUserToApprover = (user) => ({ + name: user.name, + entityName: user.name, + webUrl: user.web_url, + avatarUrl: user.avatar_url, + id: user.id, + avatarShape: 'circle', + approvals: 1, + inputDisabled: true, + type: 'user', +}); + +const mapGroupToApprover = (group) => ({ + name: group.full_name, + entityName: group.name, + webUrl: group.web_url, + avatarUrl: group.avatar_url, + id: group.id, + avatarShape: 'rect', + approvals: 1, + type: 'group', +}); + +const MIN_APPROVALS_COUNT = 1; + +const MAX_APPROVALS_COUNT = 5; + +export default { + ACCESS_LEVELS, + components: { + GlFormGroup, + GlCollapse, + GlAvatar, + GlLink, + GlFormInput, + AccessDropdown, + }, + inject: { accessLevelsData: { default: [] } }, + props: { + disabled: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + approvers: [], + approverInfo: [], + uniqueId: uniqueId('deployment-approvers-'), + }; + }, + computed: { + approvalRules() { + return this.approverInfo.map((info) => { + switch (info.type) { + case 'user': + return { user_id: info.id, required_approvals: info.approvals }; + case 'group': + return { group_id: info.id, required_approvals: info.approvals }; + case 'access': + return { access_level: info.accessLevel, required_approvals: info.approvals }; + default: + return {}; + } + }); + }, + hasSelectedApprovers() { + return Boolean(this.approvers.length); + }, + }, + watch: { + async approvers() { + try { + this.$emit('error', ''); + this.approverInfo = await Promise.all( + this.approvers.map((approver) => { + if (approver.user_id) { + return getUser(approver.user_id).then(({ data }) => mapUserToApprover(data)); + } + + if (approver.group_id) { + return Api.group(approver.group_id).then(mapGroupToApprover); + } + + return Promise.resolve({ + accessLevel: approver.access_level, + name: this.accessLevelsData.find(({ id }) => id === approver.access_level).text, + approvals: 1, + type: 'access', + }); + }), + ); + } catch (e) { + Sentry.captureException(e); + this.$emit( + 'error', + s__( + 'ProtectedEnvironments|An error occurred while fetching information on the selected approvers.', + ), + ); + } + }, + approvalRules() { + this.$emit('change', this.approvalRules); + }, + }, + methods: { + updateApprovers(permissions) { + this.approvers = permissions; + }, + isApprovalValid(approvals) { + const count = parseFloat(approvals); + return count >= MIN_APPROVALS_COUNT && count <= MAX_APPROVALS_COUNT; + }, + approvalsId(index) { + return `${this.uniqueId}-${index}`; + }, + }, + i18n: { + approverLabel: s__('ProtectedEnvironment|Approvers'), + approverHelp: s__( + 'ProtectedEnvironments|Set which groups, access levels or users are required to approve.', + ), + approvalRulesLabel: s__('ProtectedEnvironments|Approval rules'), + approvalsInvalid: s__('ProtectedEnvironments|Number of approvals must be between 1 and 5'), + }, +}; +</script> +<template> + <div> + <gl-form-group + data-testid="create-approver-dropdown" + label-for="create-approver-dropdown" + :label="$options.i18n.approverLabel" + > + <template #label-description> + {{ $options.i18n.approverHelp }} + </template> + <access-dropdown + id="create-approver-dropdown" + :access-levels-data="accessLevelsData" + :access-level="$options.ACCESS_LEVELS.DEPLOY" + :disabled="disabled" + :preselected-items="approvers" + @hidden="updateApprovers" + /> + </gl-form-group> + <gl-collapse :visible="hasSelectedApprovers"> + <span class="gl-font-weight-bold">{{ $options.i18n.approvalRulesLabel }}</span> + <div + data-testid="approval-rules" + class="protected-environment-approvers gl-display-grid gl-gap-5 gl-align-items-center" + > + <span class="protected-environment-approvers-label">{{ __('Approvers') }}</span> + <span>{{ __('Approvals required') }}</span> + <template v-for="(approver, index) in approverInfo"> + <gl-avatar + v-if="approver.avatarShape" + :key="`${index}-avatar`" + :src="approver.avatarUrl" + :size="24" + :entity-id="approver.id" + :entity-name="approver.entityName" + :shape="approver.avatarShape" + /> + <span v-else :key="`${index}-avatar`" class="gl-w-6"></span> + <gl-link v-if="approver.webUrl" :key="`${index}-name`" :href="approver.webUrl"> + {{ approver.name }} + </gl-link> + <span v-else :key="`${index}-name`">{{ approver.name }}</span> + + <gl-form-group + :key="`${index}-approvals`" + :state="isApprovalValid(approver.approvals)" + :label="$options.i18n.approverLabel" + :label-for="approvalsId(index)" + label-sr-only + > + <gl-form-input + :id="approvalsId(index)" + v-model="approver.approvals" + :disabled="approver.inputDisabled" + :state="isApprovalValid(approver.approvals)" + :name="`approval-count-${approver.name}`" + type="number" + /> + <template #invalid-feedback> + {{ $options.i18n.approvalsInvalid }} + </template> + </gl-form-group> + </template> + </div> + </gl-collapse> + </div> +</template> diff --git a/ee/app/assets/javascripts/protected_environments/create_protected_environment.vue b/ee/app/assets/javascripts/protected_environments/create_protected_environment.vue index 84fb738e83c444a6bdb3c5ca76aa15f15f296713..4a2732b606907e38a5016a8d2de1876eb6f75fdd 100644 --- a/ee/app/assets/javascripts/protected_environments/create_protected_environment.vue +++ b/ee/app/assets/javascripts/protected_environments/create_protected_environment.vue @@ -6,61 +6,31 @@ import { GlFormGroup, GlCollapse, GlCollapsibleListbox, - GlAvatar, GlLink, - GlFormInput, GlSprintf, } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; import Api from 'ee/api'; -import { getUser } from '~/rest_api'; import axios from '~/lib/utils/axios_utils'; import { __, s__ } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import AccessDropdown from '~/projects/settings/components/access_dropdown.vue'; +import AddApprovers from './add_approvers.vue'; import { ACCESS_LEVELS } from './constants'; -const mapUserToApprover = (user) => ({ - name: user.name, - entityName: user.name, - webUrl: user.web_url, - avatarUrl: user.avatar_url, - id: user.id, - avatarShape: 'circle', - approvals: 1, - inputDisabled: true, - type: 'user', -}); - -const mapGroupToApprover = (group) => ({ - name: group.full_name, - entityName: group.name, - webUrl: group.web_url, - avatarUrl: group.avatar_url, - id: group.id, - avatarShape: 'rect', - approvals: 1, - type: 'group', -}); - -const MIN_APPROVALS_COUNT = 1; - -const MAX_APPROVALS_COUNT = 5; - export default { ACCESS_LEVELS, components: { GlAlert, - GlAvatar, GlButton, GlCard, GlCollapse, GlFormGroup, GlCollapsibleListbox, GlLink, - GlFormInput, GlSprintf, AccessDropdown, + AddApprovers, }, mixins: [glFeatureFlagsMixin()], inject: { accessLevelsData: { default: [] }, apiLink: {}, docsLink: {} }, @@ -84,7 +54,6 @@ export default { environments: [], environmentsLoading: false, errorMessage: '', - approverInfo: [], alertDismissed: false, }; }, @@ -101,53 +70,6 @@ export default { hasSelectedEnvironment() { return Boolean(this.environment); }, - hasSelectedApprovers() { - return Boolean(this.approvers.length); - }, - approvalRules() { - return this.approverInfo.map((info) => { - switch (info.type) { - case 'user': - return { user_id: info.id, required_approvals: info.approvals }; - case 'group': - return { group_id: info.id, required_approvals: info.approvals }; - case 'access': - return { access_level: info.accessLevel, required_approvals: info.approvals }; - default: - return {}; - } - }); - }, - }, - watch: { - async approvers() { - try { - this.errorMessage = ''; - this.approverInfo = await Promise.all( - this.approvers.map((approver) => { - if (approver.user_id) { - return getUser(approver.user_id).then(({ data }) => mapUserToApprover(data)); - } - - if (approver.group_id) { - return Api.group(approver.group_id).then(mapGroupToApprover); - } - - return Promise.resolve({ - accessLevel: approver.access_level, - name: this.accessLevelsData.find(({ id }) => id === approver.access_level).text, - approvals: 1, - type: 'access', - }); - }), - ); - } catch (e) { - Sentry.captureException(e); - this.errorMessage = s__( - 'ProtectedEnvironments|An error occurred while fetching information on the selected approvers.', - ); - } - }, }, methods: { updateDeployers(permissions) { @@ -187,7 +109,7 @@ export default { name: this.environment, deploy_access_levels: this.deployers, ...(this.canCreateMultipleRules - ? { approval_rules: this.approvalRules } + ? { approval_rules: this.approvers } : { required_approval_count: this.approvals }), }; Api.createProtectedEnvironment(this.projectId, protectedEnvironment) @@ -199,10 +121,6 @@ export default { this.errorMessage = __('Failed to protect the environment'); }); }, - isApprovalValid(approvals) { - const count = parseFloat(approvals); - return count >= MIN_APPROVALS_COUNT && count <= MAX_APPROVALS_COUNT; - }, }, i18n: { unifiedRulesAlertHeader: s__( @@ -215,16 +133,10 @@ export default { environmentLabel: s__('ProtectedEnvironment|Select environment'), environmentText: s__('ProtectedEnvironment|Select an environment'), approvalLabel: s__('ProtectedEnvironment|Required approvals'), - approverLabel: s__('ProtectedEnvironment|Approvers'), - approverHelp: s__( - 'ProtectedEnvironments|Set which groups, access levels or users are required to approve.', - ), deployerLabel: s__('ProtectedEnvironments|Allowed to deploy'), deployerHelp: s__( 'ProtectedEnvironments|Set which groups, access levels or users that are allowed to deploy to this environment', ), - approvalRulesLabel: s__('ProtectedEnvironments|Approval rules'), - approvalsInvalid: s__('ProtectedEnvironments|Number of approvals must be between 1 and 5'), buttonText: s__('ProtectedEnvironment|Protect'), }, APPROVAL_COUNT_OPTIONS: ['0', '1', '2', '3', '4', '5'].map((value) => ({ value, text: value })), @@ -291,65 +203,11 @@ export default { @hidden="updateDeployers" /> </gl-form-group> - <gl-form-group - data-testid="create-approver-dropdown" - label-for="create-approver-dropdown" - :label="$options.i18n.approverLabel" - > - <template #label-description> - {{ $options.i18n.approverHelp }} - </template> - <access-dropdown - id="create-approver-dropdown" - :access-levels-data="accessLevelsData" - :access-level="$options.ACCESS_LEVELS.DEPLOY" - :disabled="disabled" - :preselected-items="approvers" - @hidden="updateApprovers" - /> - </gl-form-group> - <gl-collapse :visible="hasSelectedApprovers"> - <span class="gl-font-weight-bold">{{ $options.i18n.approvalRulesLabel }}</span> - <div - data-testid="approval-rules" - class="protected-environment-approvers gl-display-grid gl-gap-5 gl-align-items-center" - > - <span class="protected-environment-approvers-label">{{ __('Approvers') }}</span> - <span>{{ __('Approvals required') }}</span> - <template v-for="(approver, index) in approverInfo"> - <gl-avatar - v-if="approver.avatarShape" - :key="`${index}-avatar`" - :src="approver.avatarUrl" - :size="24" - :entity-id="approver.id" - :entity-name="approver.entityName" - :shape="approver.avatarShape" - /> - <span v-else :key="`${index}-avatar`" class="gl-w-6"></span> - <gl-link v-if="approver.webUrl" :key="`${index}-name`" :href="approver.webUrl"> - {{ approver.name }} - </gl-link> - <span v-else :key="`${index}-name`">{{ approver.name }}</span> - - <gl-form-group - :key="`${index}-approvals`" - :state="isApprovalValid(approver.approvals)" - > - <gl-form-input - v-model="approver.approvals" - :disabled="approver.inputDisabled" - :state="isApprovalValid(approver.approvals)" - :name="`approval-count-${approver.name}`" - type="number" - /> - <template #invalid-feedback> - {{ $options.i18n.approvalsInvalid }} - </template> - </gl-form-group> - </template> - </div> - </gl-collapse> + <add-approvers + :project-id="projectId" + @change="updateApprovers" + @error="errorMessage = $event" + /> </gl-collapse> </template> <template v-else> @@ -387,12 +245,3 @@ export default { </template> </gl-card> </template> -<style> -.protected-environment-approvers { - grid-template-columns: repeat(3, max-content); -} - -.protected-environment-approvers-label { - grid-column: span 2; -} -</style> diff --git a/ee/app/assets/stylesheets/page_bundles/ci_cd_settings.scss b/ee/app/assets/stylesheets/page_bundles/ci_cd_settings.scss new file mode 100644 index 0000000000000000000000000000000000000000..67445f244c43f41578c205f43f6c185f2b870f45 --- /dev/null +++ b/ee/app/assets/stylesheets/page_bundles/ci_cd_settings.scss @@ -0,0 +1,7 @@ +.protected-environment-approvers { + grid-template-columns: repeat(3, max-content); +} + +.protected-environment-approvers-label { + grid-column: span 2; +} diff --git a/ee/app/views/projects/protected_environments/_form.html.haml b/ee/app/views/projects/protected_environments/_form.html.haml index bb7b009c1b202fec5132d978df5db56228db33e9..96cf130467d71c037131e6055ffee9d75f34a2d8 100644 --- a/ee/app/views/projects/protected_environments/_form.html.haml +++ b/ee/app/views/projects/protected_environments/_form.html.haml @@ -1,3 +1,5 @@ +- add_page_specific_style 'page_bundles/ci_cd_settings' + .js-protected-environment-create-form{ data: { project_id: @project.id, api_link: help_page_path('api/protected_environments.md'), docs_link: help_page_path('ci/environments/deployment_approvals.md', anchor: 'multiple-approval-rules') } } diff --git a/ee/spec/frontend/protected_environments/add_approvers_spec.js b/ee/spec/frontend/protected_environments/add_approvers_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..546f6180a9830b24f5a39a5ea31b68612c059bca --- /dev/null +++ b/ee/spec/frontend/protected_environments/add_approvers_spec.js @@ -0,0 +1,186 @@ +import MockAdapter from 'axios-mock-adapter'; +import { nextTick } from 'vue'; +import { GlAvatar, GlFormInput } from '@gitlab/ui'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import { TEST_HOST } from 'helpers/test_constants'; +import axios from '~/lib/utils/axios_utils'; +import AccessDropdown from '~/projects/settings/components/access_dropdown.vue'; +import { ACCESS_LEVELS } from 'ee/protected_environments/constants'; +import AddApprovers from 'ee/protected_environments/add_approvers.vue'; +import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; +import { __, s__ } from '~/locale'; + +const PROJECT_ID = '0'; + +describe('ee/protected_environments/add_approvers.vue', () => { + let wrapper; + let originalGon; + let mockAxios; + + const createComponent = ({ projectId = PROJECT_ID, disabled = false } = {}) => { + wrapper = mountExtended(AddApprovers, { + propsData: { + projectId, + disabled, + }, + provide: { + accessLevelsData: [ + { + id: 40, + text: 'Maintainers', + before_divider: true, + }, + { + id: 30, + text: 'Developers + Maintainers', + before_divider: true, + }, + ], + }, + }); + }; + + const findApproverDropdown = () => wrapper.findComponent(AccessDropdown); + + const findRequiredCountForApprover = (name) => + wrapper + .findAllComponents(GlFormInput) + .wrappers.find((w) => w.attributes('name') === `approval-count-${name}`); + + beforeEach(() => { + originalGon = window.gon; + + window.gon = { + ...window.gon, + api_version: 'v4', + deploy_access_levels: { + roles: [], + }, + }; + mockAxios = new MockAdapter(axios); + }); + + afterEach(() => { + window.gon = originalGon; + }); + + it('renders a dropdown for selecting approvers', () => { + createComponent(); + + const approvers = findApproverDropdown(); + + expect(approvers.props()).toMatchObject({ + accessLevel: ACCESS_LEVELS.DEPLOY, + label: __('Select users'), + }); + }); + + it('emits an error if unable to fetch details for an approver', async () => { + mockAxios.onGet().replyOnce(HTTP_STATUS_BAD_REQUEST); + + createComponent(); + findApproverDropdown().vm.$emit('hidden', [{ group_id: 1 }]); + + await waitForPromises(); + + const [[event]] = wrapper.emitted('error').reverse(); + expect(event).toBe( + s__( + 'ProtectedEnvironments|An error occurred while fetching information on the selected approvers.', + ), + ); + }); + + it('emits an empty error value when fetching new details', async () => { + createComponent(); + findApproverDropdown().vm.$emit('hidden', [{ group_id: 1 }]); + + await waitForPromises(); + + mockAxios.onGet('/api/v4/users/1').replyOnce(HTTP_STATUS_OK, { + name: 'root', + web_url: `${TEST_HOST}/root`, + avatar_url: '/root.png', + id: 1, + }); + findApproverDropdown().vm.$emit('hidden', [{ user_id: 1 }]); + + await waitForPromises(); + + const [[event]] = wrapper.emitted('error').reverse(); + expect(event).toBe(''); + }); + + describe('information for approvers', () => { + beforeEach(() => { + mockAxios.onGet('/api/v4/users/1').replyOnce(HTTP_STATUS_OK, { + name: 'root', + web_url: `${TEST_HOST}/root`, + avatar_url: '/root.png', + id: 1, + }); + mockAxios.onGet('/api/v4/groups/1').replyOnce(HTTP_STATUS_OK, { + full_name: 'root / group', + name: 'group', + web_url: `${TEST_HOST}/root/group`, + avatar_url: '/root/group.png', + id: 1, + }); + }); + + describe.each` + type | access | details + ${'access level'} | ${{ access_level: 30 }} | ${{ name: 'Developers + Maintainers' }} + ${'group'} | ${{ group_id: 1 }} | ${{ avatarUrl: '/root/group.png', href: `${TEST_HOST}/root/group`, name: 'root / group' }} + ${'user'} | ${{ user_id: 1 }} | ${{ avatarUrl: '/root.png', href: `${TEST_HOST}/root`, name: 'root', inputDisabled: true }} + `('it displays correct information for $type', ({ access, details }) => { + beforeEach(async () => { + createComponent(); + findApproverDropdown().vm.$emit('hidden', [access]); + await nextTick(); + await waitForPromises(); + }); + + if (details.href) { + it('should link to the entity', () => { + const link = wrapper.findByRole('link', { name: details.name }); + expect(link.attributes('href')).toBe(details.href); + }); + } else { + it('should display the name of the entity', () => { + expect(wrapper.text()).toContain(details.name); + }); + } + + if (details.avatarUrl) { + it('should show an avatar', () => { + const avatar = wrapper.findComponent(GlAvatar); + expect(avatar.props('src')).toBe(details.avatarUrl); + }); + } + + if (details.inputDisabled) { + it('should have the input disabled and set to 1', () => { + const input = findRequiredCountForApprover(details.name); + expect(input.element.value).toBe('1'); + expect(input.attributes('disabled')).toBeDefined(); + }); + } else { + it('should not have the input disabled and set to 1', () => { + const input = findRequiredCountForApprover(details.name); + expect(input.element.value).toBe('1'); + expect(input.attributes('disabled')).toBeUndefined(); + }); + } + + it('emits approver info', async () => { + const input = findRequiredCountForApprover(details.name); + input.vm.$emit('input', 3); + await nextTick(); + const [[[event]]] = wrapper.emitted('change').reverse(); + expect(event).toEqual({ ...access, required_approvals: 3 }); + }); + }); + }); +}); diff --git a/ee/spec/frontend/protected_environments/create_protected_environment_spec.js b/ee/spec/frontend/protected_environments/create_protected_environment_spec.js index c357ebb4bce93a7df9253c7adc87bccfab47f0e0..c49a10dee4b41e4f1a2a7ed23363577b67f6c645 100644 --- a/ee/spec/frontend/protected_environments/create_protected_environment_spec.js +++ b/ee/spec/frontend/protected_environments/create_protected_environment_spec.js @@ -1,6 +1,6 @@ import MockAdapter from 'axios-mock-adapter'; import { nextTick } from 'vue'; -import { GlAlert, GlCollapsibleListbox, GlFormInput, GlAvatar } from '@gitlab/ui'; +import { GlAlert, GlCollapsibleListbox, GlFormInput } from '@gitlab/ui'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -9,6 +9,7 @@ import Api from 'ee/api'; import axios from '~/lib/utils/axios_utils'; import AccessDropdown from '~/projects/settings/components/access_dropdown.vue'; import { ACCESS_LEVELS } from 'ee/protected_environments/constants'; +import AddApprovers from 'ee/protected_environments/add_approvers.vue'; import CreateProtectedEnvironment from 'ee/protected_environments/create_protected_environment.vue'; import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import { __, s__ } from '~/locale'; @@ -32,12 +33,12 @@ describe('ee/protected_environments/create_protected_environment.vue', () => { wrapper.findByTestId('create-deployer-dropdown').findComponent(AccessDropdown); const findRequiredCountSelect = () => wrapper.findByTestId('create-approval-count').findComponent(GlCollapsibleListbox); - const findRequredCountForApprover = (name) => + const findRequiredCountForApprover = (name) => wrapper .findAllComponents(GlFormInput) .wrappers.find((w) => w.attributes('name') === `approval-count-${name}`); - const findApproverDropdown = () => - wrapper.findByTestId('create-approver-dropdown').findComponent(AccessDropdown); + const findAddApprovers = () => wrapper.findComponent(AddApprovers); + const findApproverDropdown = () => findAddApprovers().findComponent(AccessDropdown); const findSubmitButton = () => wrapper.findByRole('button', { name: s__('ProtectedEnvironment|Protect') }); @@ -99,6 +100,7 @@ describe('ee/protected_environments/create_protected_environment.vue', () => { findAccessDropdown().vm.$emit('hidden', deployAccessLevels); findEnvironmentsListbox().vm.$emit('select', name); findRequiredCountSelect().vm.$emit('select', requiredApprovalCount); + await findSubmitButton().vm.$emit('click'); }; @@ -196,7 +198,8 @@ describe('ee/protected_environments/create_protected_environment.vue', () => { findEnvironmentsListbox().vm.$emit('select', name); findApproverDropdown().vm.$emit('hidden', deployAccessLevels); await waitForPromises(); - findRequredCountForApprover('root').vm.$emit('input', requiredApprovalCount); + findRequiredCountForApprover('root').vm.$emit('input', requiredApprovalCount); + await nextTick(); await findSubmitButton().vm.$emit('click'); }; @@ -264,11 +267,10 @@ describe('ee/protected_environments/create_protected_environment.vue', () => { it('renders a dropdown for selecting approvers', () => { createComponent(); - const approvers = findApproverDropdown(); + const approvers = findAddApprovers(); expect(approvers.props()).toMatchObject({ - accessLevel: ACCESS_LEVELS.DEPLOY, - label: __('Select users'), + disabled: false, }); }); @@ -284,7 +286,7 @@ describe('ee/protected_environments/create_protected_environment.vue', () => { expect(Api.createProtectedEnvironment).toHaveBeenCalledWith(PROJECT_ID, { deploy_access_levels: deployAccessLevels, - approval_rules: [{ user_id: 1, required_approvals: '3' }], + approval_rules: [{ user_id: 1, required_approvals: requiredApprovalCount }], name, }); }); @@ -312,71 +314,5 @@ describe('ee/protected_environments/create_protected_environment.vue', () => { expect(window.location.reload).not.toHaveBeenCalled(); }); }); - - describe('information for approvers', () => { - unmockLocation(); - beforeEach(() => { - mockAxios.onGet('/api/v4/users/1').replyOnce(HTTP_STATUS_OK, { - name: 'root', - web_url: `${TEST_HOST}/root`, - avatar_url: '/root.png', - id: 1, - }); - mockAxios.onGet('/api/v4/groups/1').replyOnce(HTTP_STATUS_OK, { - full_name: 'root / group', - name: 'group', - web_url: `${TEST_HOST}/root/group`, - avatar_url: '/root/group.png', - id: 1, - }); - }); - - describe.each` - type | access | details - ${'access level'} | ${{ access_level: 30 }} | ${{ name: 'Developers + Maintainers' }} - ${'group'} | ${{ group_id: 1 }} | ${{ avatarUrl: '/root/group.png', href: `${TEST_HOST}/root/group`, name: 'root / group' }} - ${'user'} | ${{ user_id: 1 }} | ${{ avatarUrl: '/root.png', href: `${TEST_HOST}/root`, name: 'root', inputDisabled: true }} - `('it displays correct information for $type', ({ access, details }) => { - beforeEach(async () => { - createComponent(); - findEnvironmentsListbox().vm.$emit('select', 'production'); - findApproverDropdown().vm.$emit('hidden', [access]); - await waitForPromises(); - await nextTick(); - }); - - if (details.href) { - it('should link to the entity', () => { - const link = wrapper.findByRole('link', { name: details.name }); - expect(link.attributes('href')).toBe(details.href); - }); - } else { - it('should display the name of the entity', () => { - expect(wrapper.text()).toContain(details.name); - }); - } - - if (details.avatarUrl) { - it('should show an avatar', () => { - const avatar = wrapper.findComponent(GlAvatar); - expect(avatar.props('src')).toBe(details.avatarUrl); - }); - } - - if (details.inputDisabled) { - it('should have the input disabled and set to 1', () => { - const input = findRequredCountForApprover(details.name); - expect(input.element.value).toBe('1'); - expect(input.attributes('disabled')).toBeDefined(); - }); - } else { - it('should not have the input disabled and set to 0', () => { - const input = findRequredCountForApprover(details.name); - expect(input.element.value).toBe('1'); - expect(input.attributes('disabled')).toBeUndefined(); - }); - } - }); - }); }); });