Commit b1a21227 authored by Angelo Gulina's avatar Angelo Gulina Committed by Savas Vedova

Subscription Activation Modal: Create Modal Component

parent 1a2fe72b
<script> <script>
import { GlAlert, GlCard, GlLink, GlSprintf } from '@gitlab/ui'; import { GlCard, GlLink, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper'; import { helpPagePath } from '~/helpers/help_page_helper';
import { import { activateSubscription, howToActivateSubscription, uploadLegacyLicense } from '../constants';
activateSubscription, import SubscriptionActivationErrors from './subscription_activation_errors.vue';
CONNECTIVITY_ERROR,
connectivityErrorAlert,
connectivityIssue,
howToActivateSubscription,
uploadLegacyLicense,
} from '../constants';
import SubscriptionActivationForm from './subscription_activation_form.vue'; import SubscriptionActivationForm from './subscription_activation_form.vue';
export const adminLicenseUrl = helpPagePath('/user/admin_area/license'); export const adminLicenseUrl = helpPagePath('/user/admin_area/license');
export const troubleshootingHelpLink = helpPagePath('user/admin_area/license.html#troubleshooting');
export const subscriptionActivationHelpLink = helpPagePath('user/admin_area/license.html');
export default { export default {
name: 'SubscriptionActivationCard', name: 'SubscriptionActivationCard',
i18n: { i18n: {
activateSubscription, activateSubscription,
connectivityIssueTitle: connectivityIssue,
connectivityIssueSubtitle: connectivityErrorAlert.subtitle,
connectivityIssueHelpText: connectivityErrorAlert.helpText,
howToActivateSubscription, howToActivateSubscription,
uploadLegacyLicense, uploadLegacyLicense,
}, },
components: { components: {
GlAlert,
GlCard, GlCard,
GlLink, GlLink,
GlSprintf, GlSprintf,
SubscriptionActivationErrors,
SubscriptionActivationForm, SubscriptionActivationForm,
}, },
inject: ['licenseUploadPath'], inject: ['licenseUploadPath'],
links: { links: {
adminLicenseUrl, adminLicenseUrl,
subscriptionActivationHelpLink,
troubleshootingHelpLink,
}, },
data() { data() {
return { return {
error: null, error: null,
}; };
}, },
computed: {
hasConnectivityIssue() {
return this.error === CONNECTIVITY_ERROR;
},
},
methods: { methods: {
handleFormActivationFailure(error) { handleFormActivationFailure(error) {
this.error = error; this.error = error;
...@@ -63,32 +45,8 @@ export default { ...@@ -63,32 +45,8 @@ export default {
{{ $options.i18n.activateSubscription }} {{ $options.i18n.activateSubscription }}
</h5> </h5>
</template> </template>
<div <div v-if="error" class="gl-p-5 gl-border-b-1 gl-border-gray-100 gl-border-b-solid">
v-if="hasConnectivityIssue" <subscription-activation-errors class="mb-4" :error="error" />
class="gl-p-5 gl-border-b-1 gl-border-gray-100 gl-border-b-solid"
>
<gl-alert variant="danger" :title="$options.i18n.connectivityIssueTitle" :dismissible="false">
<gl-sprintf :message="$options.i18n.connectivityIssueSubtitle">
<template #link="{ content }">
<gl-link
:href="$options.links.subscriptionActivationHelpLink"
target="_blank"
class="gl-text-decoration-none!"
>{{ content }}
</gl-link>
</template>
</gl-sprintf>
<gl-sprintf :message="$options.i18n.connectivityIssueHelpText">
<template #link="{ content }">
<gl-link
:href="$options.links.troubleshootingHelpLink"
target="_blank"
class="gl-text-decoration-none!"
>{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert>
</div> </div>
<p class="gl-mb-0 gl-px-5 gl-pt-5"> <p class="gl-mb-0 gl-px-5 gl-pt-5">
<gl-sprintf :message="$options.i18n.howToActivateSubscription"> <gl-sprintf :message="$options.i18n.howToActivateSubscription">
......
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import {
CONNECTIVITY_ERROR,
connectivityErrorAlert,
connectivityIssue,
generalActivationError,
howToActivateSubscription,
} from '../constants';
export const troubleshootingHelpLink = helpPagePath('user/admin_area/license.html', {
anchor: 'troubleshooting',
});
export const subscriptionActivationHelpLink = helpPagePath('user/admin_area/license.html');
export default {
name: 'SubscriptionActivationErrors',
i18n: {
connectivityIssueTitle: connectivityIssue,
connectivityIssueSubtitle: connectivityErrorAlert.subtitle,
connectivityIssueHelpText: connectivityErrorAlert.helpText,
generalActivationError,
howToActivateSubscription,
},
links: {
subscriptionActivationHelpLink,
troubleshootingHelpLink,
},
components: {
GlAlert,
GlLink,
GlSprintf,
},
props: {
error: {
type: String,
required: false,
default: '',
},
},
computed: {
hasConnectivityIssue() {
return this.error === CONNECTIVITY_ERROR;
},
hasGeneralError() {
return this.error && !this.hasConnectivityIssue;
},
},
};
</script>
<template>
<div>
<gl-alert
v-if="hasConnectivityIssue"
variant="danger"
:title="$options.i18n.connectivityIssueTitle"
:dismissible="false"
data-testid="connectivity-error-alert"
>
<gl-sprintf :message="$options.i18n.connectivityIssueSubtitle">
<template #link="{ content }">
<gl-link
:href="$options.links.subscriptionActivationHelpLink"
target="_blank"
class="gl-text-decoration-none!"
>{{ content }}
</gl-link>
</template>
</gl-sprintf>
<gl-sprintf :message="$options.i18n.connectivityIssueHelpText">
<template #link="{ content }">
<gl-link
:href="$options.links.troubleshootingHelpLink"
target="_blank"
class="gl-text-decoration-none!"
>{{ content }}
</gl-link>
</template>
</gl-sprintf>
</gl-alert>
<gl-alert
v-if="hasGeneralError"
variant="danger"
:title="$options.i18n.generalActivationError"
:dismissible="false"
data-testid="general-error-alert"
>
<gl-sprintf :message="$options.i18n.howToActivateSubscription">
<template #link="{ content }">
<gl-link :href="$options.links.adminLicenseUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</gl-alert>
</div>
</template>
...@@ -30,6 +30,7 @@ const getErrorsAsData = ({ ...@@ -30,6 +30,7 @@ const getErrorsAsData = ({
}) => errors; }) => errors;
export const SUBSCRIPTION_ACTIVATION_FAILURE_EVENT = 'subscription-activation-failure'; export const SUBSCRIPTION_ACTIVATION_FAILURE_EVENT = 'subscription-activation-failure';
export const SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT = 'subscription-activation-success';
export default { export default {
name: 'CloudLicenseSubscriptionActivationForm', name: 'CloudLicenseSubscriptionActivationForm',
...@@ -52,7 +53,14 @@ export default { ...@@ -52,7 +53,14 @@ export default {
directives: { directives: {
validation: validation(), validation: validation(),
}, },
emits: [SUBSCRIPTION_ACTIVATION_FAILURE_EVENT], props: {
hideSubmitButton: {
type: Boolean,
required: false,
default: false,
},
},
emits: [SUBSCRIPTION_ACTIVATION_FAILURE_EVENT, SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT],
data() { data() {
const form = { const form = {
state: false, state: false,
...@@ -119,6 +127,7 @@ export default { ...@@ -119,6 +127,7 @@ export default {
const [error] = errors; const [error] = errors;
throw new Error(error); throw new Error(error);
} }
this.$emit(SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT);
}) })
.catch((error) => { .catch((error) => {
this.$emit(SUBSCRIPTION_ACTIVATION_FAILURE_EVENT, error.message); this.$emit(SUBSCRIPTION_ACTIVATION_FAILURE_EVENT, error.message);
...@@ -172,6 +181,7 @@ export default { ...@@ -172,6 +181,7 @@ export default {
</gl-form-group> </gl-form-group>
<gl-button <gl-button
v-if="!hideSubmitButton"
:loading="isRequestingActivation" :loading="isRequestingActivation"
category="primary" category="primary"
class="gl-mt-6 js-no-auto-disable" class="gl-mt-6 js-no-auto-disable"
......
<script>
import { GlModal } from '@gitlab/ui';
import { __ } from '~/locale';
import {
activateLabel,
activateSubscription,
subscriptionActivationInsertCode,
} 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',
components: {
GlModal,
SubscriptionActivationErrors,
SubscriptionActivationForm,
},
props: {
modalId: {
type: String,
required: true,
},
},
data() {
return {
error: null,
};
},
methods: {
handleActivationFailure(error) {
this.error = error;
},
handleActivationSuccess() {
this.$refs.modal.hide();
},
handlePrimary() {
this.$refs.form.submit();
},
removeError() {
this.error = null;
},
},
};
</script>
<template>
<gl-modal
ref="modal"
:modal-id="modalId"
:title="$options.title"
:action-cancel="$options.actionCancel"
:action-primary="$options.actionPrimary"
@primary.prevent="handlePrimary"
@hidden="removeError"
>
<subscription-activation-errors v-if="error" class="mb-4" :error="error" />
<p>{{ $options.bodyText }}</p>
<subscription-activation-form
ref="form"
:hide-submit-button="true"
@subscription-activation-failure="handleActivationFailure"
@subscription-activation-success="handleActivationSuccess"
/>
</gl-modal>
</template>
...@@ -113,6 +113,9 @@ export const buySubscriptionCard = { ...@@ -113,6 +113,9 @@ export const buySubscriptionCard = {
}; };
export const CONNECTIVITY_ERROR = 'CONNECTIVITY_ERROR'; export const CONNECTIVITY_ERROR = 'CONNECTIVITY_ERROR';
export const generalActivationError = s__(
'SuperSonics|An error occurred while activating your subscription.',
);
export const connectivityErrorAlert = { export const connectivityErrorAlert = {
subtitle: s__( subtitle: s__(
'CloudLicense|To activate your subscription, connect to GitLab servers through the %{linkStart}Cloud Sync service%{linkEnd}, a hassle-free way to manage your subscription.', 'CloudLicense|To activate your subscription, connect to GitLab servers through the %{linkStart}Cloud Sync service%{linkEnd}, a hassle-free way to manage your subscription.',
......
import { GlAlert, GlCard, GlLink, GlSprintf } from '@gitlab/ui'; import { GlCard } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import SubscriptionActivationCard, { import SubscriptionActivationCard from 'ee/pages/admin/cloud_licenses/components/subscription_activation_card.vue';
subscriptionActivationHelpLink, import SubscriptionActivationErrors from 'ee/pages/admin/cloud_licenses/components/subscription_activation_errors.vue';
troubleshootingHelpLink,
} from 'ee/pages/admin/cloud_licenses/components/subscription_activation_card.vue';
import SubscriptionActivationForm, { import SubscriptionActivationForm, {
SUBSCRIPTION_ACTIVATION_FAILURE_EVENT, SUBSCRIPTION_ACTIVATION_FAILURE_EVENT,
} from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue'; } from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue';
...@@ -14,8 +12,9 @@ describe('CloudLicenseApp', () => { ...@@ -14,8 +12,9 @@ describe('CloudLicenseApp', () => {
let wrapper; let wrapper;
const licenseUploadPath = 'license/upload'; const licenseUploadPath = 'license/upload';
const findConnectivityErrorAlert = () => wrapper.findComponent(GlAlert);
const findSubscriptionActivationForm = () => wrapper.findComponent(SubscriptionActivationForm); const findSubscriptionActivationForm = () => wrapper.findComponent(SubscriptionActivationForm);
const findSubscriptionActivationErrors = () =>
wrapper.findComponent(SubscriptionActivationErrors);
const findUploadLink = () => wrapper.findByTestId('upload-license-link'); const findUploadLink = () => wrapper.findByTestId('upload-license-link');
const createComponent = ({ props = {}, stubs = {}, provide = {} } = {}) => { const createComponent = ({ props = {}, stubs = {}, provide = {} } = {}) => {
...@@ -46,7 +45,7 @@ describe('CloudLicenseApp', () => { ...@@ -46,7 +45,7 @@ describe('CloudLicenseApp', () => {
}); });
it('does not show any alert', () => { it('does not show any alert', () => {
expect(findConnectivityErrorAlert().exists()).toBe(false); expect(findSubscriptionActivationErrors().exists()).toBe(false);
}); });
describe('with an upload legacy license link', () => { describe('with an upload legacy license link', () => {
...@@ -76,7 +75,7 @@ describe('CloudLicenseApp', () => { ...@@ -76,7 +75,7 @@ describe('CloudLicenseApp', () => {
describe('when the forms emits a connectivity error', () => { describe('when the forms emits a connectivity error', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ stubs: { GlSprintf } }); createComponent();
findSubscriptionActivationForm().vm.$emit( findSubscriptionActivationForm().vm.$emit(
SUBSCRIPTION_ACTIVATION_FAILURE_EVENT, SUBSCRIPTION_ACTIVATION_FAILURE_EVENT,
CONNECTIVITY_ERROR, CONNECTIVITY_ERROR,
...@@ -84,14 +83,11 @@ describe('CloudLicenseApp', () => { ...@@ -84,14 +83,11 @@ describe('CloudLicenseApp', () => {
}); });
it('shows an alert component', () => { it('shows an alert component', () => {
expect(findConnectivityErrorAlert().exists()).toBe(true); expect(findSubscriptionActivationErrors().exists()).toBe(true);
}); });
it('shows some help links', () => { it('passes the correct error to the component', () => {
const alert = findConnectivityErrorAlert(); expect(findSubscriptionActivationErrors().props('error')).toBe(CONNECTIVITY_ERROR);
expect(alert.findAll(GlLink).at(0).attributes('href')).toBe(subscriptionActivationHelpLink);
expect(alert.findAll(GlLink).at(1).attributes('href')).toBe(troubleshootingHelpLink);
}); });
}); });
}); });
import { GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import {
subscriptionActivationHelpLink,
troubleshootingHelpLink,
} from 'ee/pages/admin/cloud_licenses/components/subscription_activation_card.vue';
import SubscriptionActivationErrors from 'ee/pages/admin/cloud_licenses/components/subscription_activation_errors.vue';
import {
CONNECTIVITY_ERROR,
connectivityIssue,
generalActivationError,
} from 'ee/pages/admin/cloud_licenses/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
describe('SubscriptionActivationErrors', () => {
let wrapper;
const findConnectivityErrorAlert = () => wrapper.findByTestId('connectivity-error-alert');
const findGeneralErrorAlert = () => wrapper.findByTestId('general-error-alert');
const createComponent = ({ props = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(SubscriptionActivationErrors, {
propsData: {
...props,
},
stubs: { GlSprintf },
}),
);
};
afterEach(() => {
wrapper.destroy();
});
describe('connectivity error', () => {
beforeEach(() => {
createComponent({ props: { error: CONNECTIVITY_ERROR } });
});
it('shows the alert', () => {
expect(findConnectivityErrorAlert().props('title')).toBe(connectivityIssue);
});
it('shows some help links', () => {
const alert = findConnectivityErrorAlert();
expect(alert.findAll(GlLink).at(0).props('href')).toBe(subscriptionActivationHelpLink);
expect(alert.findAll(GlLink).at(1).props('href')).toBe(troubleshootingHelpLink);
});
it('does not show other alerts', () => {
expect(findGeneralErrorAlert().exists()).toBe(false);
});
});
describe('general error', () => {
beforeEach(() => {
createComponent({ props: { error: 'A fake error' } });
});
it('shows a general error alert', () => {
expect(findGeneralErrorAlert().props('title')).toBe(generalActivationError);
});
it('shows a a text to help the user', () => {
expect(findGeneralErrorAlert().text()).toBe('Learn how to activate your subscription.');
});
it('does not show the connectivity alert', () => {
expect(findConnectivityErrorAlert().exists()).toBe(false);
});
});
});
...@@ -3,6 +3,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils'; ...@@ -3,6 +3,7 @@ import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import SubscriptionActivationForm, { import SubscriptionActivationForm, {
SUBSCRIPTION_ACTIVATION_FAILURE_EVENT, SUBSCRIPTION_ACTIVATION_FAILURE_EVENT,
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
} from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue'; } from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue';
import { import {
CONNECTIVITY_ERROR, CONNECTIVITY_ERROR,
...@@ -135,6 +136,10 @@ describe('CloudLicenseApp', () => { ...@@ -135,6 +136,10 @@ describe('CloudLicenseApp', () => {
}, },
}); });
}); });
it('emits a successful event', () => {
expect(wrapper.emitted(SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT)).toEqual([[]]);
});
}); });
describe('when the mutation is not successful', () => { describe('when the mutation is not successful', () => {
......
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SubscriptionActivationErrors from 'ee/pages/admin/cloud_licenses/components/subscription_activation_errors.vue';
import SubscriptionActivationForm, {
SUBSCRIPTION_ACTIVATION_FAILURE_EVENT,
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
} from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue';
import SubscriptionActivationModal from 'ee/pages/admin/cloud_licenses/components/subscription_activation_modal.vue';
import {
activateSubscription,
CONNECTIVITY_ERROR,
subscriptionActivationInsertCode,
} from 'ee/pages/admin/cloud_licenses/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { preventDefault } from '../../test_helpers';
describe('SubscriptionActivationModal', () => {
let wrapper;
const modalId = 'fake-modal-id';
const findGlModal = () => wrapper.findComponent(GlModal);
const findSubscriptionActivationErrors = () =>
wrapper.findComponent(SubscriptionActivationErrors);
const findSubscriptionActivationForm = () => wrapper.findComponent(SubscriptionActivationForm);
const createComponent = ({ props = {}, stubs = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(SubscriptionActivationModal, {
propsData: {
modalId,
...props,
},
stubs,
}),
);
};
afterEach(() => {
wrapper.destroy();
});
describe('idle state', () => {
beforeEach(() => {
createComponent();
});
it('has an id', () => {
expect(findGlModal().attributes('modalid')).toBe(modalId);
});
it('shows a description text', () => {
expect(wrapper.text()).toContain(subscriptionActivationInsertCode);
});
it('shows a title', () => {
expect(findGlModal().attributes('title')).toBe(activateSubscription);
});
it('shows the subscription activation form', () => {
expect(findSubscriptionActivationForm().exists()).toBe(true);
});
it('hides the form default button', () => {
expect(findSubscriptionActivationForm().props('hideSubmitButton')).toBe(true);
});
it('does not show any error', () => {
expect(findSubscriptionActivationErrors().exists()).toBe(false);
});
});
describe('subscription activation', () => {
const fakeEvent = 'fake-modal-event';
const hiddenEven = 'hidden';
describe('when submitting the form', () => {
beforeEach(() => {
createComponent();
jest
.spyOn(wrapper.vm, 'handlePrimary')
.mockImplementation(() => wrapper.vm.$emit(fakeEvent));
findGlModal().vm.$emit('primary', { preventDefault });
});
it('emits the correct event', () => {
expect(wrapper.emitted(fakeEvent)).toEqual([[]]);
});
});
describe('successful activation', () => {
beforeEach(() => {
createComponent({ stubs: { GlModal } });
jest
.spyOn(wrapper.vm.$refs.modal, 'hide')
.mockImplementation(() => wrapper.vm.$emit(hiddenEven));
findSubscriptionActivationForm().vm.$emit(SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT);
});
it('it emits a hidden event', () => {
expect(wrapper.emitted(hiddenEven)).toEqual([[]]);
});
});
describe('failing activation', () => {
beforeEach(() => {
createComponent();
findSubscriptionActivationForm().vm.$emit(
SUBSCRIPTION_ACTIVATION_FAILURE_EVENT,
CONNECTIVITY_ERROR,
);
});
it('passes the correct props', () => {
expect(findSubscriptionActivationErrors().props('error')).toBe(CONNECTIVITY_ERROR);
});
it('resets the component state when closing', async () => {
await findGlModal().vm.$emit('hidden');
expect(findSubscriptionActivationErrors().exists()).toBe(false);
});
});
});
});
...@@ -31085,6 +31085,9 @@ msgstr "" ...@@ -31085,6 +31085,9 @@ msgstr ""
msgid "SuperSonics|Activated on" msgid "SuperSonics|Activated on"
msgstr "" msgstr ""
msgid "SuperSonics|An error occurred while activating your subscription."
msgstr ""
msgid "SuperSonics|Expires on" msgid "SuperSonics|Expires on"
msgstr "" msgstr ""
......
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