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';
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 ApprovalsSummary from './approvals_summary.vue';
import ApprovalsSummaryOptional from './approvals_summary_optional.vue';
import ApprovalsFooter from './approvals_footer.vue';
import { FETCH_LOADING, FETCH_ERROR, APPROVE_ERROR, UNAPPROVE_ERROR } from '../messages';
......@@ -17,6 +18,7 @@ export default {
MrWidgetContainer,
MrWidgetIcon,
ApprovalsSummary,
ApprovalsSummaryOptional,
ApprovalsFooter,
GlButton,
GlLoadingIcon,
......@@ -44,7 +46,13 @@ export default {
return this.mr.approvals && this.mr.approvals.has_approval_rules;
},
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() {
return this.mr.approvals.user_has_approved;
......@@ -59,16 +67,16 @@ export default {
return this.userHasApproved && !this.userCanApprove && this.mr.state !== 'merged';
},
action() {
if (this.showApprove && this.mr.approvals.approved) {
return {
text: s__('mrWidget|Approve additionally'),
variant: 'primary',
inverted: true,
action: () => this.approve(),
};
} else if (this.showApprove) {
if (this.showApprove) {
const inverted = this.mr.approvals.approved;
const text =
this.mr.approvals.approved && this.approvedBy.length
? s__('mrWidget|Approve additionally')
: s__('mrWidget|Approve');
return {
text: s__('mrWidget|Approve'),
text,
inverted,
variant: 'primary',
action: () => this.approve(),
};
......@@ -83,6 +91,9 @@ export default {
return null;
},
hasAction() {
return !!this.action;
},
},
watch: {
isExpanded(val) {
......@@ -160,7 +171,13 @@ export default {
<gl-loading-icon v-if="isApproving" inline />
{{ action.text }}
</gl-button>
<approvals-summary-optional
v-if="isOptional"
:can-approve="hasAction"
:help-path="mr.approvalsHelpPath"
/>
<approvals-summary
v-else
:approved="mr.approvals.approved"
:approvals-left="mr.approvals.approvals_left"
: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';
import eventHub from '~/vue_merge_request_widget/event_hub';
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 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 {
FETCH_LOADING,
......@@ -12,6 +13,7 @@ import {
} from 'ee/vue_merge_request_widget/components/approvals/messages';
const localVue = createLocalVue();
const TEST_HELP_PATH = 'help/path';
const testApprovedBy = () => [1, 7, 10].map(id => ({ id }));
const testApprovals = () => ({
has_approval_rules: true,
......@@ -64,6 +66,7 @@ describe('EE MRWidget approvals', () => {
};
};
const findSummary = () => wrapper.find(ApprovalsSummary);
const findOptionalSummary = () => wrapper.find(ApprovalsSummaryOptional);
const findFooter = () => wrapper.find(ApprovalsFooter);
beforeEach(() => {
......@@ -75,6 +78,7 @@ describe('EE MRWidget approvals', () => {
});
mr = {
...jasmine.createSpyObj('Store', ['setApprovals', 'setApprovalRules']),
approvalsHelpPath: TEST_HELP_PATH,
approvals: testApprovals(),
approvalRules: [],
isOpen: true,
......@@ -185,17 +189,39 @@ describe('EE MRWidget approvals', () => {
});
describe('and MR is approved', () => {
beforeEach(done => {
beforeEach(() => {
mr.approvals.approved = true;
createComponent();
waitForTick(done);
});
it('approve additionally action is rendered', () => {
expect(findActionData()).toEqual({
variant: 'primary',
text: 'Approve additionally',
inverted: true,
describe('with no approvers', () => {
beforeEach(done => {
mr.approvals.approved_by = [];
createComponent();
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', () => {
});
});
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', () => {
beforeEach(done => {
createComponent();
......@@ -325,6 +395,7 @@ describe('EE MRWidget approvals', () => {
const expected = testApprovals();
const summary = findSummary();
expect(findOptionalSummary().exists()).toBe(false);
expect(summary.exists()).toBe(true);
expect(summary.props()).toEqual(
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