Commit 92512f8a authored by Zamir Martins's avatar Zamir Martins Committed by Paul Slaughter

Add approvers select dropdown

which allows users and groups to be added
as approvers.

EE: true
parent e5f32c63
<script> <script>
import { import { GlSprintf, GlForm, GlFormInput, GlModalDirective } from '@gitlab/ui';
GlSprintf,
GlForm,
GlFormInput,
GlModalDirective,
GlToken,
GlAvatarLabeled,
} from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { AVATAR_SHAPE_OPTION_CIRCLE, AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; import { AVATAR_SHAPE_OPTION_CIRCLE, AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import { groupApprovers, decomposeApprovers, USER_TYPE } from './lib/actions'; import ApproversSelect from 'ee/approvals/components/approvers_select.vue';
import ApproversList from 'ee/approvals/components/approvers_list.vue';
import { groupApprovers, decomposeApprovers, groupIds, userIds, USER_TYPE } from './lib/actions';
export default { export default {
components: { components: {
GlSprintf, GlSprintf,
GlForm, GlForm,
GlFormInput, GlFormInput,
GlToken, ApproversSelect,
GlAvatarLabeled, ApproversList,
}, },
directives: { directives: {
GlModalDirective, GlModalDirective,
...@@ -37,12 +32,24 @@ export default { ...@@ -37,12 +32,24 @@ export default {
return { return {
action: { ...this.initAction }, action: { ...this.initAction },
approvers: groupApprovers(this.existingApprovers), approvers: groupApprovers(this.existingApprovers),
approversToAdd: [],
}; };
}, },
computed: {
groupIds() {
return groupIds(this.approvers);
},
userIds() {
return userIds(this.approvers);
},
},
watch: { watch: {
approvers(values) { approvers(values) {
this.action = decomposeApprovers(this.action, values); this.action = decomposeApprovers(this.action, values);
}, },
approversToAdd(val) {
this.approvers.push(val[0]);
},
action: { action: {
handler(values) { handler(values) {
this.$emit('changed', values); this.$emit('changed', values);
...@@ -59,6 +66,9 @@ export default { ...@@ -59,6 +66,9 @@ export default {
(approver) => approver.type !== removedApprover.type || approver.id !== removedApprover.id, (approver) => approver.type !== removedApprover.type || approver.id !== removedApprover.id,
); );
}, },
selectedApprover(approver) {
this.approvers.push(approver);
},
avatarShape(approver) { avatarShape(approver) {
return this.isUser(approver) ? AVATAR_SHAPE_OPTION_CIRCLE : AVATAR_SHAPE_OPTION_RECT; return this.isUser(approver) ? AVATAR_SHAPE_OPTION_CIRCLE : AVATAR_SHAPE_OPTION_RECT;
}, },
...@@ -73,7 +83,7 @@ export default { ...@@ -73,7 +83,7 @@ export default {
addAnApprover: s__('ScanResultPolicy|add an approver'), addAnApprover: s__('ScanResultPolicy|add an approver'),
}, },
humanizedTemplate: s__( humanizedTemplate: s__(
'ScanResultPolicy|%{thenLabelStart}Then%{thenLabelEnd} Require approval from %{approvalsRequired} of the following approvers: %{approvers}', 'ScanResultPolicy|%{thenLabelStart}Then%{thenLabelEnd} Require approval from %{approvalsRequired} of the following approvers:',
), ),
}; };
</script> </script>
...@@ -100,25 +110,18 @@ export default { ...@@ -100,25 +110,18 @@ export default {
@input="approvalsRequiredChanged" @input="approvalsRequiredChanged"
/> />
</template> </template>
<template #approvers>
<gl-token
v-for="approver in approvers"
:key="approver.type + approver.id"
class="gl-ml-3"
@close="removeApprover(approver)"
>
<gl-avatar-labeled
:src="approver.avatar_url"
:size="24"
:shape="avatarShape(approver)"
:label="approverName(approver)"
:entity-name="approver.name"
:alt="approver.name"
/>
</gl-token>
</template>
</gl-sprintf> </gl-sprintf>
<div class="gl-bg-white gl-w-full gl-display-flex">
<approvers-select
v-model="approversToAdd"
:skip-user-ids="userIds"
:skip-group-ids="groupIds"
:project-id="projectId"
/>
</div>
<div class="gl-bg-white gl-w-full gl-mt-3 gl-border gl-rounded-base gl-overflow-auto h-12em">
<approvers-list v-model="approvers" />
</div>
</gl-form> </gl-form>
</div> </div>
</template> </template>
import { GlFormInput, GlToken } from '@gitlab/ui'; import { GlFormInput } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import MockAdapter from 'axios-mock-adapter';
import PolicyActionBuilder from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/policy_action_builder.vue'; import PolicyActionBuilder from 'ee/threat_monitoring/components/policy_editor/scan_result_policy/policy_action_builder.vue';
import ApproversSelect from 'ee/approvals/components/approvers_select.vue';
import ApproversList from 'ee/approvals/components/approvers_list.vue';
import ApproversListItem from 'ee/approvals/components/approvers_list_item.vue';
import axios from '~/lib/utils/axios_utils';
const APPROVER_1 = { const APPROVER_1 = {
id: 1, id: 1,
...@@ -21,6 +26,15 @@ const APPROVER_2 = { ...@@ -21,6 +26,15 @@ const APPROVER_2 = {
avatar_url: '', avatar_url: '',
}; };
const NEW_APPROVER = {
id: 3,
name: 'name2',
state: 'active',
username: 'username2',
web_url: '',
avatar_url: '',
};
const APPROVERS = [APPROVER_1, APPROVER_2]; const APPROVERS = [APPROVER_1, APPROVER_2];
const APPROVERS_IDS = APPROVERS.map((approver) => approver.id); const APPROVERS_IDS = APPROVERS.map((approver) => approver.id);
...@@ -32,6 +46,7 @@ const ACTION = { ...@@ -32,6 +46,7 @@ const ACTION = {
describe('PolicyActionBuilder', () => { describe('PolicyActionBuilder', () => {
let wrapper; let wrapper;
let mock;
const factory = (propsData = {}) => { const factory = (propsData = {}) => {
wrapper = mount(PolicyActionBuilder, { wrapper = mount(PolicyActionBuilder, {
...@@ -47,14 +62,28 @@ describe('PolicyActionBuilder', () => { ...@@ -47,14 +62,28 @@ describe('PolicyActionBuilder', () => {
}; };
const findApprovalsRequiredInput = () => wrapper.findComponent(GlFormInput); const findApprovalsRequiredInput = () => wrapper.findComponent(GlFormInput);
const findAllGlTokens = () => wrapper.findAllComponents(GlToken); const findApproversList = () => wrapper.findComponent(ApproversList);
const findAddApproversSelect = () => wrapper.findComponent(ApproversSelect);
const findAllApproversItem = () => wrapper.findAllComponents(ApproversListItem);
beforeEach(() => {
mock = new MockAdapter(axios);
mock.onGet('/api/undefined/projects/1/users').reply(200);
mock.onGet('/api/undefined/projects/1/groups.json').reply(200);
});
afterEach(() => {
wrapper.destroy();
mock.restore();
});
it('renders approvals required form input, gl-tokens', async () => { it('renders approvals required form input, approvers list and approvers select', async () => {
factory(); factory();
await nextTick(); await nextTick();
expect(findApprovalsRequiredInput().exists()).toBe(true); expect(findApprovalsRequiredInput().exists()).toBe(true);
expect(findAllGlTokens().length).toBe(APPROVERS.length); expect(findApproversList().exists()).toBe(true);
expect(findAddApproversSelect().exists()).toBe(true);
}); });
it('triggers an update when changing approvals required', async () => { it('triggers an update when changing approvals required', async () => {
...@@ -71,17 +100,17 @@ describe('PolicyActionBuilder', () => { ...@@ -71,17 +100,17 @@ describe('PolicyActionBuilder', () => {
]); ]);
}); });
it('removes one approver when triggering a gl-token', async () => { it('removes one approver when triggering a remove button click', async () => {
factory(); factory();
await nextTick(); await nextTick();
const allGlTokens = findAllGlTokens(); const allApproversItems = findAllApproversItem();
const glToken = allGlTokens.at(0); const approversItem = allApproversItems.at(0);
const approversLengthMinusOne = APPROVERS.length - 1; const approversLengthMinusOne = APPROVERS.length - 1;
expect(allGlTokens.length).toBe(APPROVERS.length); expect(allApproversItems.length).toBe(APPROVERS.length);
await glToken.vm.$emit('close', { ...APPROVER_1, type: 'user' }); await approversItem.vm.$emit('remove', { ...APPROVER_1, type: 'user' });
expect(wrapper.emitted().changed).toEqual([ expect(wrapper.emitted().changed).toEqual([
[ [
...@@ -92,6 +121,29 @@ describe('PolicyActionBuilder', () => { ...@@ -92,6 +121,29 @@ describe('PolicyActionBuilder', () => {
}, },
], ],
]); ]);
expect(findAllGlTokens()).toHaveLength(approversLengthMinusOne); expect(findAllApproversItem()).toHaveLength(approversLengthMinusOne);
});
it('adds one approver when triggering a new user is selected', async () => {
factory();
await nextTick();
const allApproversItems = findAllApproversItem();
const approversLengthPlusOne = APPROVERS.length + 1;
expect(allApproversItems.length).toBe(APPROVERS.length);
await findAddApproversSelect().vm.$emit('input', [{ ...NEW_APPROVER, type: 'user' }]);
expect(wrapper.emitted().changed).toEqual([
[
{
approvals_required: ACTION.approvals_required,
user_approvers_ids: [APPROVER_1.id, APPROVER_2.id, NEW_APPROVER.id],
group_approvers_ids: [],
},
],
]);
expect(findAllApproversItem()).toHaveLength(approversLengthPlusOne);
}); });
}); });
...@@ -31965,7 +31965,7 @@ msgstr "" ...@@ -31965,7 +31965,7 @@ msgstr ""
msgid "ScanResultPolicy|%{ifLabelStart}if%{ifLabelEnd} %{scanners} scan in an open merge request targeting the %{branches} branch(es) finds %{vulnerabilitiesAllowed} or more %{severities} vulnerabilities that are %{vulnerabilityStates}" msgid "ScanResultPolicy|%{ifLabelStart}if%{ifLabelEnd} %{scanners} scan in an open merge request targeting the %{branches} branch(es) finds %{vulnerabilitiesAllowed} or more %{severities} vulnerabilities that are %{vulnerabilityStates}"
msgstr "" msgstr ""
msgid "ScanResultPolicy|%{thenLabelStart}Then%{thenLabelEnd} Require approval from %{approvalsRequired} of the following approvers: %{approvers}" msgid "ScanResultPolicy|%{thenLabelStart}Then%{thenLabelEnd} Require approval from %{approvalsRequired} of the following approvers:"
msgstr "" msgstr ""
msgid "ScanResultPolicy|add an approver" msgid "ScanResultPolicy|add an approver"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment