Commit a618af50 authored by Angelo Gulina's avatar Angelo Gulina Committed by Brandon Labuschagne

Add mid-term banner

parent 09d4ec74
<script>
import { GlBanner, GlLink, GlSprintf } from '@gitlab/ui';
import {
activateCloudLicense,
subscriptionBannerText,
subscriptionBannerTitle,
} from '../constants';
export const ACTIVATE_SUBSCRIPTION_EVENT = 'activate-subscription';
export default {
name: 'SubscriptionActivationBanner',
i18n: {
bannerText: subscriptionBannerText,
buttonText: activateCloudLicense,
title: subscriptionBannerTitle,
},
components: {
GlBanner,
GlLink,
GlSprintf,
},
inject: ['congratulationSvgPath', 'customersPortalUrl'],
methods: {
handlePrimary() {
this.$emit(ACTIVATE_SUBSCRIPTION_EVENT);
},
},
};
</script>
<template>
<gl-banner
:button-text="$options.i18n.buttonText"
:title="$options.i18n.title"
variant="promotion"
:svg-path="congratulationSvgPath"
@primary="handlePrimary"
>
<p>
<gl-sprintf :message="$options.i18n.bannerText">
<template #blogPostLink="{ content }">
<gl-link href="#" target="_blank">{{ content }}</gl-link>
</template>
<template #portalLink="{ content }">
<gl-link :href="customersPortalUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</gl-banner>
</template>
......@@ -22,11 +22,19 @@ export default {
SubscriptionActivationErrors,
SubscriptionActivationForm,
},
model: {
prop: 'visible',
event: 'change',
},
props: {
modalId: {
type: String,
required: true,
},
visible: {
type: Boolean,
required: true,
},
},
data() {
return {
......@@ -38,7 +46,10 @@ export default {
this.error = error;
},
handleActivationSuccess() {
this.$refs.modal.hide();
this.$emit('change', false);
},
handleChange(event) {
this.$emit('change', event);
},
handlePrimary() {
this.$refs.form.submit();
......@@ -52,13 +63,14 @@ export default {
<template>
<gl-modal
ref="modal"
:visible="visible"
:modal-id="modalId"
:title="$options.title"
:action-cancel="$options.actionCancel"
:action-primary="$options.actionPrimary"
@primary.prevent="handlePrimary"
@hidden="removeError"
@change="handleChange"
>
<subscription-activation-errors v-if="error" class="mb-4" :error="error" />
<p>{{ $options.bodyText }}</p>
......
......@@ -66,6 +66,7 @@ export default {
shouldShowNotifications: false,
subscriptionSyncStatus: null,
subscriptionDetailsFields,
activationModalVisible: false,
};
},
computed: {
......@@ -138,7 +139,11 @@ export default {
<template>
<div>
<subscription-activation-modal v-if="hasSubscription" :modal-id="$options.modal.id" />
<subscription-activation-modal
v-if="hasSubscription"
v-model="activationModalVisible"
:modal-id="$options.modal.id"
/>
<subscription-sync-notifications
v-if="shouldShowNotifications"
class="mb-4"
......
......@@ -137,3 +137,7 @@ export const connectivityErrorAlert = {
),
};
export const supportLink = 'https://about.gitlab.com/support/#contact-support';
export const subscriptionBannerTitle = s__('SuperSonics|Cloud licensing');
export const subscriptionBannerText = s__(
"SuperSonics|Cloud licensing is now available. It's an easier way to activate instances and manage subscriptions. Read more about it in our %{blogPostLinkStart}blog post%{blogPostLinkEnd}. Activation codes are available in the %{portalLinkStart}Customers Portal%{portalLinkEnd}.",
);
......@@ -25,6 +25,7 @@ export default () => {
const {
buySubscriptionPath,
congratulationSvgPath,
customersPortalUrl,
freeTrialPath,
hasActiveLicense,
......@@ -41,6 +42,7 @@ export default () => {
apolloProvider,
provide: {
buySubscriptionPath,
congratulationSvgPath,
connectivityHelpURL,
customersPortalUrl,
freeTrialPath,
......
......@@ -60,7 +60,8 @@ module LicenseHelper
has_active_license: (has_active_license? ? 'true' : 'false'),
license_upload_path: new_admin_license_path,
license_remove_path: admin_license_path,
subscription_sync_path: sync_seat_link_admin_license_path
subscription_sync_path: sync_seat_link_admin_license_path,
congratulation_svg_path: image_path('illustrations/illustration-congratulation-purchase.svg')
}
end
......
import { GlBanner, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SubscriptionActivationBanner, {
ACTIVATE_SUBSCRIPTION_EVENT,
} from 'ee/admin/subscriptions/show/components/subscription_activation_banner.vue';
import {
activateCloudLicense,
subscriptionBannerText,
subscriptionBannerTitle,
} from 'ee/admin/subscriptions/show/constants';
describe('SubscriptionActivationBanner', () => {
let wrapper;
const findBanner = () => wrapper.findComponent(GlBanner);
const findLink = (at) => wrapper.findAllComponents(GlLink).at(at);
const customersPortalUrl = 'customers.dot';
const congratulationSvgPath = '/path/to/svg';
const createComponent = () => {
wrapper = shallowMount(SubscriptionActivationBanner, {
provide: {
congratulationSvgPath,
customersPortalUrl,
},
stubs: {
GlSprintf,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
it('provides the correct props to the banner', () => {
expect(findBanner().props()).toMatchObject({
buttonText: activateCloudLicense,
title: subscriptionBannerTitle,
svgPath: congratulationSvgPath,
});
});
it('contains help text', () => {
expect(findBanner().text()).toMatchInterpolatedText(subscriptionBannerText);
});
it('contains a link to the blog post', () => {
expect(findLink(0).attributes('href')).toBe('#');
});
it('contains a link to the customers portal', () => {
expect(findLink(1).attributes('href')).toBe(customersPortalUrl);
});
it('emits an event when the primary button is clicked', () => {
expect(wrapper.emitted(ACTIVATE_SUBSCRIPTION_EVENT)).toBeUndefined();
findBanner().vm.$emit('primary');
expect(wrapper.emitted(ACTIVATE_SUBSCRIPTION_EVENT)).toEqual([[]]);
});
});
......@@ -23,14 +23,14 @@ describe('SubscriptionActivationModal', () => {
wrapper.findComponent(SubscriptionActivationErrors);
const findSubscriptionActivationForm = () => wrapper.findComponent(SubscriptionActivationForm);
const createComponent = ({ props = {}, stubs = {} } = {}) => {
const createComponent = ({ props = {} } = {}) => {
wrapper = extendedWrapper(
shallowMount(SubscriptionActivationModal, {
propsData: {
modalId,
visible: false,
...props,
},
stubs,
}),
);
};
......@@ -67,11 +67,18 @@ describe('SubscriptionActivationModal', () => {
it('does not show any error', () => {
expect(findSubscriptionActivationErrors().exists()).toBe(false);
});
it('emits a change event', () => {
expect(wrapper.emitted('change')).toBeUndefined();
findGlModal().vm.$emit('change', false);
expect(wrapper.emitted('change')).toEqual([[false]]);
});
});
describe('subscription activation', () => {
const fakeEvent = 'fake-modal-event';
const hiddenEven = 'hidden';
describe('when submitting the form', () => {
beforeEach(() => {
......@@ -89,15 +96,19 @@ describe('SubscriptionActivationModal', () => {
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);
createComponent({ props: { visible: true } });
});
it('it emits a hidden event', () => {
expect(wrapper.emitted(hiddenEven)).toEqual([[]]);
it('provides the correct prop to the modal', () => {
expect(findGlModal().props('visible')).toBe(true);
});
it('hides the modal', () => {
expect(wrapper.emitted('change')).toBeUndefined();
findSubscriptionActivationForm().vm.$emit(SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT);
expect(wrapper.emitted('change')).toEqual([[false]]);
});
});
......
......@@ -138,11 +138,18 @@ describe('Subscription Breakdown', () => {
});
it('presents a subscription activation modal', () => {
expect(findSubscriptionActivationModal().exists()).toBe(true);
expect(findSubscriptionActivationModal().props()).toMatchObject({
modalId,
visible: false,
});
});
it('passes the correct modal id', () => {
expect(findSubscriptionActivationModal().attributes('modalid')).toBe(modalId);
it('updates visible of subscription activation modal when change emitted', async () => {
findSubscriptionActivationModal().vm.$emit('change', true);
await wrapper.vm.$nextTick();
expect(findSubscriptionActivationModal().props('visible')).toBe(true);
});
describe('footer buttons', () => {
......@@ -279,9 +286,7 @@ describe('Subscription Breakdown', () => {
expect(findSubscriptionSyncNotifications().exists()).toBe(false);
});
it('shows a modal', () => {
const props = { subscription: { ...licenseFile } };
createComponent({ props, stubs: { GlCard, SubscriptionDetailsCard } });
it('shows modal when active subscription action clicked', () => {
findActivateSubscriptionAction().vm.$emit('click');
expect(glModalDirective).toHaveBeenCalledWith(modalId);
......
......@@ -99,7 +99,8 @@ RSpec.describe LicenseHelper do
buy_subscription_path: 'subscriptions_plans_url',
subscription_sync_path: sync_seat_link_admin_license_path,
license_upload_path: new_admin_license_path,
license_remove_path: admin_license_path })
license_remove_path: admin_license_path,
congratulation_svg_path: helper.image_path('illustrations/illustration-congratulation-purchase.svg') })
end
end
......@@ -113,7 +114,8 @@ RSpec.describe LicenseHelper do
buy_subscription_path: 'subscriptions_plans_url',
subscription_sync_path: sync_seat_link_admin_license_path,
license_upload_path: new_admin_license_path,
license_remove_path: admin_license_path })
license_remove_path: admin_license_path,
congratulation_svg_path: helper.image_path('illustrations/illustration-congratulation-purchase.svg') })
end
end
end
......
......@@ -31425,6 +31425,12 @@ msgstr ""
msgid "SuperSonics|Cloud license"
msgstr ""
msgid "SuperSonics|Cloud licensing"
msgstr ""
msgid "SuperSonics|Cloud licensing is now available. It's an easier way to activate instances and manage subscriptions. Read more about it in our %{blogPostLinkStart}blog post%{blogPostLinkEnd}. Activation codes are available in the %{portalLinkStart}Customers Portal%{portalLinkEnd}."
msgstr ""
msgid "SuperSonics|Expires on"
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