From ee3c331834b40aeef57450aa289d7b202d7f83e6 Mon Sep 17 00:00:00 2001 From: Andrew Fontaine <afontaine@gitlab.com> Date: Wed, 22 Feb 2023 07:11:35 +0000 Subject: [PATCH] Refactor out new deployment approval rule form This is going to be used as part of adding new approval rules to existing protected environments. It handles the fetching of details for selected access levels, displaying avatars (if any), and setting required approval counts for the rule. --- config/application.rb | 1 + .../protected_environments/add_approvers.vue | 206 ++++++++++++++++++ .../create_protected_environment.vue | 167 +------------- .../page_bundles/ci_cd_settings.scss | 7 + .../protected_environments/_form.html.haml | 2 + .../add_approvers_spec.js | 186 ++++++++++++++++ .../create_protected_environment_spec.js | 86 +------- 7 files changed, 421 insertions(+), 234 deletions(-) create mode 100644 ee/app/assets/javascripts/protected_environments/add_approvers.vue create mode 100644 ee/app/assets/stylesheets/page_bundles/ci_cd_settings.scss create mode 100644 ee/spec/frontend/protected_environments/add_approvers_spec.js diff --git a/config/application.rb b/config/application.rb index a1ed5fbd58aa..a9008e387cfa 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 000000000000..7f972fc7f22f --- /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 84fb738e83c4..4a2732b60690 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 000000000000..67445f244c43 --- /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 bb7b009c1b20..96cf130467d7 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 000000000000..546f6180a983 --- /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 c357ebb4bce9..c49a10dee4b4 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(); - }); - } - }); - }); }); }); -- GitLab