Commit e7d936f5 authored by Paul Slaughter's avatar Paul Slaughter

Add approvals summary optional component

For the most part, this is copied from the old approvals component.
Added unit tests to test show / hide of the normal summary
vs. the optional summary.
parent ee217fc0
...@@ -7,6 +7,7 @@ import eventHub from '~/vue_merge_request_widget/event_hub'; ...@@ -7,6 +7,7 @@ import eventHub from '~/vue_merge_request_widget/event_hub';
import MrWidgetContainer from '~/vue_merge_request_widget/components/mr_widget_container.vue'; import MrWidgetContainer from '~/vue_merge_request_widget/components/mr_widget_container.vue';
import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue'; import MrWidgetIcon from '~/vue_merge_request_widget/components/mr_widget_icon.vue';
import ApprovalsSummary from './approvals_summary.vue'; import ApprovalsSummary from './approvals_summary.vue';
import ApprovalsSummaryOptional from './approvals_summary_optional.vue';
import ApprovalsFooter from './approvals_footer.vue'; import ApprovalsFooter from './approvals_footer.vue';
import { FETCH_LOADING, FETCH_ERROR, APPROVE_ERROR, UNAPPROVE_ERROR } from '../messages'; import { FETCH_LOADING, FETCH_ERROR, APPROVE_ERROR, UNAPPROVE_ERROR } from '../messages';
...@@ -17,6 +18,7 @@ export default { ...@@ -17,6 +18,7 @@ export default {
MrWidgetContainer, MrWidgetContainer,
MrWidgetIcon, MrWidgetIcon,
ApprovalsSummary, ApprovalsSummary,
ApprovalsSummaryOptional,
ApprovalsFooter, ApprovalsFooter,
GlButton, GlButton,
GlLoadingIcon, GlLoadingIcon,
...@@ -44,7 +46,13 @@ export default { ...@@ -44,7 +46,13 @@ export default {
return this.mr.approvals && this.mr.approvals.has_approval_rules; return this.mr.approvals && this.mr.approvals.has_approval_rules;
}, },
approvedBy() { approvedBy() {
return this.mr.approvals.approved_by.map(x => x.user); return this.mr.approvals ? this.mr.approvals.approved_by.map(x => x.user) : [];
},
approvalsRequired() {
return this.mr.approvals ? this.mr.approvals.approvals_required : 0;
},
isOptional() {
return !this.approvedBy.length && !this.approvalsRequired;
}, },
userHasApproved() { userHasApproved() {
return this.mr.approvals.user_has_approved; return this.mr.approvals.user_has_approved;
...@@ -59,16 +67,16 @@ export default { ...@@ -59,16 +67,16 @@ export default {
return this.userHasApproved && !this.userCanApprove && this.mr.state !== 'merged'; return this.userHasApproved && !this.userCanApprove && this.mr.state !== 'merged';
}, },
action() { action() {
if (this.showApprove && this.mr.approvals.approved) { if (this.showApprove) {
return { const inverted = this.mr.approvals.approved;
text: s__('mrWidget|Approve additionally'), const text =
variant: 'primary', this.mr.approvals.approved && this.approvedBy.length
inverted: true, ? s__('mrWidget|Approve additionally')
action: () => this.approve(), : s__('mrWidget|Approve');
};
} else if (this.showApprove) {
return { return {
text: s__('mrWidget|Approve'), text,
inverted,
variant: 'primary', variant: 'primary',
action: () => this.approve(), action: () => this.approve(),
}; };
...@@ -83,6 +91,9 @@ export default { ...@@ -83,6 +91,9 @@ export default {
return null; return null;
}, },
hasAction() {
return !!this.action;
},
}, },
watch: { watch: {
isExpanded(val) { isExpanded(val) {
...@@ -160,7 +171,13 @@ export default { ...@@ -160,7 +171,13 @@ export default {
<gl-loading-icon v-if="isApproving" inline /> <gl-loading-icon v-if="isApproving" inline />
{{ action.text }} {{ action.text }}
</gl-button> </gl-button>
<approvals-summary-optional
v-if="isOptional"
:can-approve="hasAction"
:help-path="mr.approvalsHelpPath"
/>
<approvals-summary <approvals-summary
v-else
:approved="mr.approvals.approved" :approved="mr.approvals.approved"
:approvals-left="mr.approvals.approvals_left" :approvals-left="mr.approvals.approvals_left"
:rules-left="mr.approvals.approvalRuleNamesLeft" :rules-left="mr.approvals.approvalRuleNamesLeft"
......
<script>
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import { OPTIONAL, OPTIONAL_CAN_APPROVE } from '../messages';
export default {
components: {
GlLink,
Icon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
canApprove: {
type: Boolean,
required: true,
},
helpPath: {
type: String,
required: false,
default: '',
},
},
computed: {
message() {
return this.canApprove ? OPTIONAL_CAN_APPROVE : OPTIONAL;
},
},
};
</script>
<template>
<div class="d-flex align-items-center">
<span class="text-muted">{{ message }}</span>
<gl-link
v-if="canApprove && helpPath"
v-gl-tooltip
:href="helpPath"
:title="__('About this feature')"
target="_blank"
class="d-flex-center pl-1"
>
<icon name="question-o" />
</gl-link>
</div>
</template>
---
title: Add 'No approvals required' view to approval rules (behind feature flag)
merge_request: 9899
author:
type: fixed
...@@ -3,6 +3,7 @@ import { GlButton, GlLoadingIcon } from '@gitlab/ui'; ...@@ -3,6 +3,7 @@ import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import eventHub from '~/vue_merge_request_widget/event_hub'; import eventHub from '~/vue_merge_request_widget/event_hub';
import Approvals from 'ee/vue_merge_request_widget/components/approvals/multiple_rule/approvals.vue'; import Approvals from 'ee/vue_merge_request_widget/components/approvals/multiple_rule/approvals.vue';
import ApprovalsSummary from 'ee/vue_merge_request_widget/components/approvals/multiple_rule/approvals_summary.vue'; import ApprovalsSummary from 'ee/vue_merge_request_widget/components/approvals/multiple_rule/approvals_summary.vue';
import ApprovalsSummaryOptional from 'ee/vue_merge_request_widget/components/approvals/multiple_rule/approvals_summary_optional.vue';
import ApprovalsFooter from 'ee/vue_merge_request_widget/components/approvals/multiple_rule/approvals_footer.vue'; import ApprovalsFooter from 'ee/vue_merge_request_widget/components/approvals/multiple_rule/approvals_footer.vue';
import { import {
FETCH_LOADING, FETCH_LOADING,
...@@ -12,6 +13,7 @@ import { ...@@ -12,6 +13,7 @@ import {
} from 'ee/vue_merge_request_widget/components/approvals/messages'; } from 'ee/vue_merge_request_widget/components/approvals/messages';
const localVue = createLocalVue(); const localVue = createLocalVue();
const TEST_HELP_PATH = 'help/path';
const testApprovedBy = () => [1, 7, 10].map(id => ({ id })); const testApprovedBy = () => [1, 7, 10].map(id => ({ id }));
const testApprovals = () => ({ const testApprovals = () => ({
has_approval_rules: true, has_approval_rules: true,
...@@ -64,6 +66,7 @@ describe('EE MRWidget approvals', () => { ...@@ -64,6 +66,7 @@ describe('EE MRWidget approvals', () => {
}; };
}; };
const findSummary = () => wrapper.find(ApprovalsSummary); const findSummary = () => wrapper.find(ApprovalsSummary);
const findOptionalSummary = () => wrapper.find(ApprovalsSummaryOptional);
const findFooter = () => wrapper.find(ApprovalsFooter); const findFooter = () => wrapper.find(ApprovalsFooter);
beforeEach(() => { beforeEach(() => {
...@@ -75,6 +78,7 @@ describe('EE MRWidget approvals', () => { ...@@ -75,6 +78,7 @@ describe('EE MRWidget approvals', () => {
}); });
mr = { mr = {
...jasmine.createSpyObj('Store', ['setApprovals', 'setApprovalRules']), ...jasmine.createSpyObj('Store', ['setApprovals', 'setApprovalRules']),
approvalsHelpPath: TEST_HELP_PATH,
approvals: testApprovals(), approvals: testApprovals(),
approvalRules: [], approvalRules: [],
isOpen: true, isOpen: true,
...@@ -185,17 +189,39 @@ describe('EE MRWidget approvals', () => { ...@@ -185,17 +189,39 @@ describe('EE MRWidget approvals', () => {
}); });
describe('and MR is approved', () => { describe('and MR is approved', () => {
beforeEach(done => { beforeEach(() => {
mr.approvals.approved = true; mr.approvals.approved = true;
createComponent();
waitForTick(done);
}); });
it('approve additionally action is rendered', () => { describe('with no approvers', () => {
expect(findActionData()).toEqual({ beforeEach(done => {
variant: 'primary', mr.approvals.approved_by = [];
text: 'Approve additionally', createComponent();
inverted: true, waitForTick(done);
});
it('approve action (with inverted) is rendered', () => {
expect(findActionData()).toEqual({
variant: 'primary',
text: 'Approve',
inverted: true,
});
});
});
describe('with approvers', () => {
beforeEach(done => {
mr.approvals.approved_by = [{ user: { id: 7 } }];
createComponent();
waitForTick(done);
});
it('approve additionally action is rendered', () => {
expect(findActionData()).toEqual({
variant: 'primary',
text: 'Approve additionally',
inverted: true,
});
}); });
}); });
}); });
...@@ -315,6 +341,50 @@ describe('EE MRWidget approvals', () => { ...@@ -315,6 +341,50 @@ describe('EE MRWidget approvals', () => {
}); });
}); });
describe('approvals optional summary', () => {
describe('when no approvals required and no approvers', () => {
beforeEach(() => {
mr.approvals.approved_by = [];
mr.approvals.approvals_required = 0;
mr.approvals.user_has_approved = false;
});
describe('and can approve', () => {
beforeEach(done => {
mr.approvals.user_can_approve = true;
createComponent();
waitForTick(done);
});
it('is shown', () => {
expect(findSummary().exists()).toBe(false);
expect(findOptionalSummary().props()).toEqual({
canApprove: true,
helpPath: TEST_HELP_PATH,
});
});
});
describe('and cannot approve', () => {
beforeEach(done => {
mr.approvals.user_can_approve = false;
createComponent();
waitForTick(done);
});
it('is shown', () => {
expect(findSummary().exists()).toBe(false);
expect(findOptionalSummary().props()).toEqual({
canApprove: false,
helpPath: TEST_HELP_PATH,
});
});
});
});
});
describe('approvals summary', () => { describe('approvals summary', () => {
beforeEach(done => { beforeEach(done => {
createComponent(); createComponent();
...@@ -325,6 +395,7 @@ describe('EE MRWidget approvals', () => { ...@@ -325,6 +395,7 @@ describe('EE MRWidget approvals', () => {
const expected = testApprovals(); const expected = testApprovals();
const summary = findSummary(); const summary = findSummary();
expect(findOptionalSummary().exists()).toBe(false);
expect(summary.exists()).toBe(true); expect(summary.exists()).toBe(true);
expect(summary.props()).toEqual( expect(summary.props()).toEqual(
jasmine.objectContaining({ jasmine.objectContaining({
......
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
import {
OPTIONAL,
OPTIONAL_CAN_APPROVE,
} from 'ee/vue_merge_request_widget/components/approvals/messages';
import ApprovalsSummaryOptional from 'ee/vue_merge_request_widget/components/approvals/multiple_rule/approvals_summary_optional.vue';
const localVue = createLocalVue();
const TEST_HELP_PATH = 'help/path';
describe('EE MRWidget approvals summary optional', () => {
let wrapper;
const createComponent = (props = {}) => {
wrapper = shallowMount(localVue.extend(ApprovalsSummaryOptional), {
propsData: props,
sync: false,
localVue,
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const findHelpLink = () => wrapper.find(GlLink);
describe('when can approve', () => {
beforeEach(() => {
createComponent({ canApprove: true, helpPath: TEST_HELP_PATH });
});
it('shows optional can approve message', () => {
expect(wrapper.text()).toEqual(OPTIONAL_CAN_APPROVE);
});
it('shows help link', () => {
const link = findHelpLink();
expect(link.exists()).toBe(true);
expect(link.attributes('href')).toBe(TEST_HELP_PATH);
});
});
describe('when cannot approve', () => {
beforeEach(() => {
createComponent({ canApprove: false, helpPath: TEST_HELP_PATH });
});
it('shows optional message', () => {
expect(wrapper.text()).toEqual(OPTIONAL);
});
it('does not show help link', () => {
expect(findHelpLink().exists()).toBe(false);
});
});
});
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