Commit 67403800 authored by Michael Lunøe's avatar Michael Lunøe Committed by Peter Hegman

Feat(Cloud Activation Form Modal): loading button

This MR adds loading in the modal button, when the
form is being submitted, so the user knows that a
request is ongoing.

Changelog: added
EE: true
parent ea0528bd
......@@ -64,9 +64,6 @@ export default {
return {
currentSubscription: {},
activationNotification: null,
activationListeners: {
[SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT]: this.displayActivationNotification,
},
subscriptionHistory: [],
};
},
......@@ -78,6 +75,11 @@ export default {
return this.hasActiveLicense || this.hasValidSubscriptionData;
},
},
created() {
this.$options.activationListeners = {
[SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT]: this.displayActivationNotification,
};
},
methods: {
displayActivationNotification(license) {
if (isInFuture(new Date(license.startsAt))) {
......@@ -123,14 +125,14 @@ export default {
v-if="canShowSubscriptionDetails"
:subscription="currentSubscription"
:subscription-list="subscriptionHistory"
v-on="activationListeners"
v-on="$options.activationListeners"
/>
<div v-else class="row">
<div class="col-12 col-lg-8 offset-lg-2">
<h3 class="gl-mb-7 gl-mt-6 gl-text-center" data-testid="subscription-activation-title">
{{ $options.i18n.noActiveSubscription }}
</h3>
<subscription-activation-card v-on="activationListeners" />
<subscription-activation-card v-on="$options.activationListeners" />
<div class="row gl-mt-7">
<div class="col-lg-6">
<subscription-trial-card />
......
......@@ -36,10 +36,12 @@ export default {
data() {
return {
error: null,
activationListeners: {
[SUBSCRIPTION_ACTIVATION_FAILURE_EVENT]: this.handleActivationFailure,
[SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT]: this.handleActivationSuccess,
},
};
},
created() {
this.$options.activationListeners = {
[SUBSCRIPTION_ACTIVATION_FAILURE_EVENT]: this.handleActivationFailure,
[SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT]: this.handleActivationSuccess,
};
},
methods: {
......@@ -76,7 +78,7 @@ export default {
</template>
</gl-sprintf>
</p>
<subscription-activation-form class="gl-p-5" v-on="activationListeners" />
<subscription-activation-form class="gl-p-5" v-on="$options.activationListeners" />
<template #footer>
<gl-link
v-if="licenseUploadPath"
......
......@@ -15,6 +15,7 @@ import {
INVALID_CODE_ERROR_MESSAGE,
SUBSCRIPTION_ACTIVATION_FAILURE_EVENT,
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT,
subscriptionActivationForm,
subscriptionQueries,
} from '../constants';
......@@ -93,6 +94,7 @@ export default {
submit() {
if (!this.form.state) {
this.form.showValidation = true;
this.$emit(SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT);
return;
}
this.form.showValidation = false;
......@@ -127,6 +129,7 @@ export default {
this.handleError(error);
})
.finally(() => {
this.$emit(SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT);
this.isLoading = false;
});
},
......
<script>
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
import {
activateLabel,
cancelLabel,
activateSubscription,
subscriptionActivationInsertCode,
SUBSCRIPTION_ACTIVATION_FAILURE_EVENT,
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT,
} from '../constants';
import SubscriptionActivationErrors from './subscription_activation_errors.vue';
import SubscriptionActivationForm from './subscription_activation_form.vue';
export default {
actionCancel: { text: __('Cancel') },
actionPrimary: {
text: activateLabel,
},
bodyText: subscriptionActivationInsertCode,
title: activateSubscription,
name: 'SubscriptionActivationModal',
......@@ -41,13 +38,37 @@ export default {
data() {
return {
error: null,
activationListeners: {
[SUBSCRIPTION_ACTIVATION_FAILURE_EVENT]: this.handleActivationFailure,
[SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT]: this.handleActivationSuccess,
},
isLoading: false,
};
},
computed: {
actionCancel() {
return { text: cancelLabel };
},
actionPrimary() {
return {
text: activateLabel,
attributes: [
{
variant: 'confirm',
category: 'primary',
loading: this.isLoading,
},
],
};
},
},
created() {
this.$options.activationListeners = {
[SUBSCRIPTION_ACTIVATION_FAILURE_EVENT]: this.handleActivationFailure,
[SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT]: this.handleActivationSuccess,
[SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT]: this.handleActivationFinalized,
};
},
methods: {
handleActivationFinalized() {
this.isLoading = false;
},
handleActivationFailure(error) {
this.error = error;
},
......@@ -60,6 +81,7 @@ export default {
this.$emit('change', event);
},
handlePrimary() {
this.isLoading = true;
this.$refs.form.submit();
},
removeError() {
......@@ -74,8 +96,8 @@ export default {
:visible="visible"
:modal-id="modalId"
:title="$options.title"
:action-cancel="$options.actionCancel"
:action-primary="$options.actionPrimary"
:action-cancel="actionCancel"
:action-primary="actionPrimary"
@primary.prevent="handlePrimary"
@hidden="removeError"
@change="handleChange"
......@@ -85,7 +107,7 @@ export default {
<subscription-activation-form
ref="form"
:hide-submit-button="true"
v-on="activationListeners"
v-on="$options.activationListeners"
/>
</gl-modal>
</template>
......@@ -19,6 +19,7 @@ export const subscriptionActivationInsertCode = __(
export const howToActivateSubscription = s__(
'SuperSonics|Learn how to %{linkStart}activate your subscription%{linkEnd}.',
);
export const cancelLabel = __('Cancel');
export const activateLabel = s__('SuperSonics|Activate');
export const activateSubscription = s__('SuperSonics|Activate subscription');
export const activateCloudLicense = s__('SuperSonics|Activate cloud license');
......@@ -129,6 +130,7 @@ export const buySubscriptionCard = {
export const SUBSCRIPTION_ACTIVATION_FAILURE_EVENT = 'subscription-activation-failure';
export const SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT = 'subscription-activation-success';
export const SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT = 'subscription-activation-finalized';
export const INVALID_CODE_ERROR_MESSAGE = 'invalid activation code';
export const CONNECTIVITY_ERROR = 'CONNECTIVITY_ERROR';
......
......@@ -7,12 +7,14 @@ import {
INVALID_CODE_ERROR,
SUBSCRIPTION_ACTIVATION_FAILURE_EVENT,
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT,
subscriptionQueries,
subscriptionActivationForm,
} from 'ee/admin/subscriptions/show/constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { preventDefault, stopPropagation } from '../../test_helpers';
import {
activateLicenseMutationResponse,
......@@ -121,6 +123,10 @@ describe('SubscriptionActivationForm', () => {
expect(mutationMock).toHaveBeenCalledTimes(0);
});
it(`emits the ${SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT} event`, () => {
expect(wrapper.emitted(SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT).length).toBe(1);
});
describe('adds text that does not match the pattern', () => {
beforeEach(async () => {
await findActivationCodeInput().vm.$emit('input', `${fakeActivationCode}2021-asdf`);
......@@ -152,6 +158,12 @@ describe('SubscriptionActivationForm', () => {
subscriptionActivationForm.acceptTermsFeedback,
);
});
it(`emits the ${SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT} event`, async () => {
findActivateSubscriptionForm().vm.$emit('submit', createFakeEvent());
await waitForPromises();
expect(wrapper.emitted(SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT).length).toBe(2);
});
});
});
});
......
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { mount, shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import SubscriptionActivationErrors from 'ee/admin/subscriptions/show/components/subscription_activation_errors.vue';
import SubscriptionActivationForm from 'ee/admin/subscriptions/show/components/subscription_activation_form.vue';
import SubscriptionActivationModal from 'ee/admin/subscriptions/show/components/subscription_activation_modal.vue';
......@@ -10,29 +11,29 @@ import {
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
subscriptionActivationInsertCode,
} from 'ee/admin/subscriptions/show/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { preventDefault } from '../../test_helpers';
import { activateLicenseMutationResponse } from '../mock_data';
const modalId = 'fake-modal-id';
describe('SubscriptionActivationModal', () => {
let wrapper;
const modalId = 'fake-modal-id';
const findGlModal = () => wrapper.findComponent(GlModal);
const firePrimaryEvent = () => findGlModal().vm.$emit('primary', { preventDefault });
const findSubscriptionActivationErrors = () =>
wrapper.findComponent(SubscriptionActivationErrors);
const findSubscriptionActivationForm = () => wrapper.findComponent(SubscriptionActivationForm);
const createComponent = ({ props = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(SubscriptionActivationModal, {
propsData: {
modalId,
visible: false,
...props,
},
}),
);
const createComponent = (options = {}) => {
const { props = {}, mountFn = shallowMount } = options;
wrapper = mountFn(SubscriptionActivationModal, {
propsData: {
modalId,
visible: false,
...props,
},
});
};
afterEach(() => {
......@@ -78,19 +79,34 @@ describe('SubscriptionActivationModal', () => {
});
describe('subscription activation', () => {
const fakeEvent = 'fake-modal-event';
let submitSpy;
describe('when the "primary" button is clicked', () => {
beforeEach(async () => {
createComponent({ mountFn: mount, props: { visible: true } });
// Wait for $refs.form to be present
await nextTick();
submitSpy = jest.spyOn(wrapper.vm.$refs.form, 'submit');
});
describe('when submitting the form', () => {
beforeEach(() => {
createComponent();
jest
.spyOn(wrapper.vm, 'handlePrimary')
.mockImplementation(() => wrapper.vm.$emit(fakeEvent));
findGlModal().vm.$emit('primary', { preventDefault });
it('submits the form', () => {
firePrimaryEvent();
expect(submitSpy).toHaveBeenCalled();
});
it('shows loading in the button', async () => {
submitSpy.mockImplementation(() => {});
firePrimaryEvent();
// Wait for submit to emit event
await nextTick();
expect(findGlModal().props('actionPrimary').attributes[0].loading).toEqual(true);
});
it('emits the correct event', () => {
expect(wrapper.emitted(fakeEvent)).toEqual([[]]);
it('stops loading in the button', async () => {
firePrimaryEvent();
// Wait for submit to emit event
await nextTick();
expect(findGlModal().props('actionPrimary').attributes[0].loading).toEqual(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