Commit c8d18216 authored by Michael Lunøe's avatar Michael Lunøe

Merge branch '337027-update-subscription-history-table' into 'master'

Show future subscription notification and history table in various places

See merge request gitlab-org/gitlab!77304
parents a26b7400 7fa25e26
......@@ -3,8 +3,6 @@ import { GlAlert, GlButton, GlSprintf } from '@gitlab/ui';
import { isInFuture } from '~/lib/utils/datetime/date_calculation_utility';
import { sprintf } from '~/locale';
import {
activateSubscription,
noActiveSubscription,
subscriptionActivationNotificationText,
subscriptionActivationFutureDatedNotificationTitle,
subscriptionActivationFutureDatedNotificationMessage,
......@@ -20,10 +18,8 @@ import {
import getCurrentLicense from '../graphql/queries/get_current_license.query.graphql';
import getPastLicenseHistory from '../graphql/queries/get_past_license_history.query.graphql';
import getFutureLicenseHistory from '../graphql/queries/get_future_license_history.query.graphql';
import SubscriptionActivationCard from './subscription_activation_card.vue';
import SubscriptionBreakdown from './subscription_breakdown.vue';
import SubscriptionPurchaseCard from './subscription_purchase_card.vue';
import SubscriptionTrialCard from './subscription_trial_card.vue';
import NoActiveSubscription from './no_active_subscription.vue';
export default {
name: 'CloudLicenseApp',
......@@ -31,15 +27,11 @@ export default {
GlAlert,
GlButton,
GlSprintf,
SubscriptionActivationCard,
SubscriptionBreakdown,
SubscriptionPurchaseCard,
SubscriptionTrialCard,
NoActiveSubscription,
},
i18n: {
activateSubscription,
exportLicenseUsageBtnText,
noActiveSubscription,
subscriptionMainTitle,
subscriptionHistoryFailedTitle,
subscriptionHistoryFailedMessage,
......@@ -176,21 +168,10 @@ export default {
:subscription-list="subscriptionHistory"
v-on="$options.activationListeners"
/>
<div v-else class="row">
<div class="col-12">
<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="$options.activationListeners" />
<div class="row gl-mt-7">
<div class="col-lg-6">
<subscription-trial-card />
</div>
<div class="col-lg-6">
<subscription-purchase-card />
</div>
</div>
</div>
</div>
<no-active-subscription
v-else
:subscription-list="subscriptionHistory"
v-on="$options.activationListeners"
/>
</div>
</template>
<script>
import { GlAlert, GlSprintf } from '@gitlab/ui';
import { minBy } from 'lodash';
import { isInFuture } from '~/lib/utils/datetime/date_calculation_utility';
import { instanceHasFutureLicenseBanner, noActiveSubscription } from '../constants';
import SubscriptionActivationCard from './subscription_activation_card.vue';
import SubscriptionDetailsHistory from './subscription_details_history.vue';
import SubscriptionPurchaseCard from './subscription_purchase_card.vue';
import SubscriptionTrialCard from './subscription_trial_card.vue';
export default {
name: 'NoActiveSubscription',
components: {
GlAlert,
GlSprintf,
SubscriptionActivationCard,
SubscriptionPurchaseCard,
SubscriptionTrialCard,
SubscriptionDetailsHistory,
},
i18n: {
instanceHasFutureLicenseBanner,
noActiveSubscription,
},
props: {
subscriptionList: {
type: Array,
required: true,
},
},
computed: {
hasItems() {
return Boolean(this.subscriptionList.length);
},
nextFutureDatedLicenseDate() {
const futureItems = this.subscriptionList.filter((license) =>
isInFuture(new Date(license.startsAt)),
);
const nextFutureDatedItem = minBy(futureItems, (license) => new Date(license.startsAt));
return nextFutureDatedItem?.startsAt;
},
hasFutureDatedLicense() {
return Boolean(this.nextFutureDatedLicenseDate);
},
},
};
</script>
<template>
<div class="row">
<div class="col-12">
<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="$listeners" />
<gl-alert
v-if="hasFutureDatedLicense"
:title="$options.i18n.instanceHasFutureLicenseBanner.title"
:dismissible="false"
class="gl-mt-5"
variant="info"
data-testid="subscription-future-licenses-alert"
>
<gl-sprintf :message="$options.i18n.instanceHasFutureLicenseBanner.message">
<template #date>{{ nextFutureDatedLicenseDate }}</template>
</gl-sprintf>
</gl-alert>
<div v-if="hasItems && hasFutureDatedLicense" class="col-12 gl-mt-5">
<subscription-details-history :subscription-list="subscriptionList" />
</div>
<div class="row gl-mt-7">
<div class="col-lg-6 gl-sm-mb-7">
<subscription-trial-card />
</div>
<div class="col-lg-6">
<subscription-purchase-card />
</div>
</div>
<div v-if="hasItems && !hasFutureDatedLicense" class="col-12 gl-mt-5">
<subscription-details-history :subscription-list="subscriptionList" />
</div>
</div>
</div>
</template>
......@@ -158,3 +158,10 @@ export const subscriptionBannerText = s__(
export const subscriptionBannerBlogPostUrl =
'https://about.gitlab.com/blog/2021/07/20/improved-billing-and-subscription-management/';
export const exportLicenseUsageBtnText = s__('SuperSonics|Export license usage file');
export const instanceHasFutureLicenseBanner = {
title: s__('SuperSonics|You have a future dated license'),
message: s__(
'SuperSonics|You have added a license that activates on %{date}. Please see the subscription history table below for more details.',
),
};
import { GlButton, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import SubscriptionManagementApp from 'ee/admin/subscriptions/show/components/app.vue';
import SubscriptionActivationCard from 'ee/admin/subscriptions/show/components/subscription_activation_card.vue';
import SubscriptionBreakdown from 'ee/admin/subscriptions/show/components/subscription_breakdown.vue';
import NoActiveSubscription from 'ee_else_ce/admin/subscriptions/show/components/no_active_subscription.vue';
import {
noActiveSubscription,
subscriptionActivationNotificationText,
subscriptionActivationFutureDatedNotificationTitle,
subscriptionHistoryFailedTitle,
......@@ -35,10 +34,8 @@ describe('SubscriptionManagementApp', () => {
let wrapper;
const findActivateSubscriptionCard = () => wrapper.findComponent(SubscriptionActivationCard);
const findSubscriptionBreakdown = () => wrapper.findComponent(SubscriptionBreakdown);
const findSubscriptionActivationTitle = () =>
wrapper.findByTestId('subscription-activation-title');
const findNoActiveSubscription = () => wrapper.findComponent(NoActiveSubscription);
const findSubscriptionMainTitle = () => wrapper.findByTestId('subscription-main-title');
const findSubscriptionActivationSuccessAlert = () =>
wrapper.findByTestId('subscription-activation-success-alert');
......@@ -77,8 +74,8 @@ describe('SubscriptionManagementApp', () => {
wrapper.destroy();
});
describe('when failing to fetch subcriptions', () => {
describe('when failing to fetch history subcriptions', () => {
describe('when failing to fetch subscriptions', () => {
describe('when failing to fetch history subscriptions', () => {
describe.each`
currentFails | pastFails | futureFails
${true} | ${false} | ${false}
......@@ -139,45 +136,52 @@ describe('SubscriptionManagementApp', () => {
});
});
describe('Subscription Activation Form', () => {
it('shows the main title', () => {
currentSubscriptionResolver = jest
.fn()
.mockResolvedValue({ data: { currentLicense: license.ULTIMATE } });
pastSubscriptionsResolver = jest.fn().mockResolvedValue({
data: { licenseHistoryEntries: { nodes: subscriptionPastHistory } },
});
futureSubscriptionsResolver = jest.fn().mockResolvedValue({
data: { subscriptionFutureEntries: { nodes: subscriptionFutureHistory } },
});
createComponent({}, [
currentSubscriptionResolver,
pastSubscriptionsResolver,
futureSubscriptionsResolver,
]);
expect(findSubscriptionMainTitle().text()).toBe(subscriptionMainTitle);
it('shows the main title', () => {
currentSubscriptionResolver = jest
.fn()
.mockResolvedValue({ data: { currentLicense: license.ULTIMATE } });
pastSubscriptionsResolver = jest.fn().mockResolvedValue({
data: { licenseHistoryEntries: { nodes: subscriptionPastHistory } },
});
futureSubscriptionsResolver = jest.fn().mockResolvedValue({
data: { subscriptionFutureEntries: { nodes: subscriptionFutureHistory } },
});
createComponent({}, [
currentSubscriptionResolver,
pastSubscriptionsResolver,
futureSubscriptionsResolver,
]);
expect(findSubscriptionMainTitle().text()).toBe(subscriptionMainTitle);
});
describe('Subscription Activation Form', () => {
describe('without an active license', () => {
beforeEach(() => {
beforeEach(async () => {
currentSubscriptionResolver = jest
.fn()
.mockResolvedValue({ data: { currentLicense: null } });
pastSubscriptionsResolver = jest
.fn()
.mockResolvedValue({ data: { licenseHistoryEntries: { nodes: [] } } });
futureSubscriptionsResolver = jest
.fn()
.mockResolvedValue({ data: { subscriptionFutureEntries: { nodes: [] } } });
createComponent({}, [
pastSubscriptionsResolver = jest.fn().mockResolvedValue({
data: { licenseHistoryEntries: { nodes: subscriptionPastHistory } },
});
futureSubscriptionsResolver = jest.fn().mockResolvedValue({
data: { subscriptionFutureEntries: { nodes: subscriptionFutureHistory } },
});
createComponent({ hasActiveLicense: false }, [
currentSubscriptionResolver,
pastSubscriptionsResolver,
futureSubscriptionsResolver,
]);
await waitForPromises();
});
it('shows the no active subscription state', () => {
expect(findNoActiveSubscription().exists()).toBe(true);
});
it('shows a title saying there is no active subscription', () => {
expect(findSubscriptionActivationTitle().text()).toBe(noActiveSubscription);
it('passes correct data to the no subscription state', () => {
expect(findNoActiveSubscription().props()).toMatchObject({
subscriptionList: [...subscriptionFutureHistory, ...subscriptionPastHistory],
});
});
it('queries for the past history', () => {
......@@ -188,10 +192,6 @@ describe('SubscriptionManagementApp', () => {
expect(futureSubscriptionsResolver).toHaveBeenCalledTimes(1);
});
it('shows the subscription activation form', () => {
expect(findActivateSubscriptionCard().exists()).toBe(true);
});
it('does not show the activation success notification', () => {
expect(findSubscriptionActivationSuccessAlert().exists()).toBe(false);
});
......@@ -202,11 +202,11 @@ describe('SubscriptionManagementApp', () => {
describe('activating the license', () => {
it('shows the activation success notification', async () => {
findActivateSubscriptionCard().vm.$emit(
findNoActiveSubscription().vm.$emit(
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
license.ULTIMATE,
);
await waitForPromises();
await nextTick();
expect(findSubscriptionActivationSuccessAlert().props('title')).toBe(
subscriptionActivationNotificationText,
......@@ -214,10 +214,12 @@ describe('SubscriptionManagementApp', () => {
});
it('shows the future dated activation success notification', async () => {
await findActivateSubscriptionCard().vm.$emit(
findNoActiveSubscription().vm.$emit(
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
license.ULTIMATE_FUTURE_DATED,
);
await nextTick();
expect(findSubscriptionActivationSuccessAlert().props('title')).toBe(
subscriptionActivationFutureDatedNotificationTitle,
);
......@@ -261,10 +263,12 @@ describe('SubscriptionManagementApp', () => {
});
it('shows the activation success notification', async () => {
await findSubscriptionBreakdown().vm.$emit(
findSubscriptionBreakdown().vm.$emit(
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
license.ULTIMATE,
);
await nextTick();
expect(findSubscriptionActivationSuccessAlert().props('title')).toBe(
subscriptionActivationNotificationText,
);
......@@ -281,10 +285,12 @@ describe('SubscriptionManagementApp', () => {
});
it('calls refetch to update local state', async () => {
await findSubscriptionBreakdown().vm.$emit(
findSubscriptionBreakdown().vm.$emit(
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
license.ULTIMATE_FUTURE_DATED,
);
await nextTick();
expect(wrapper.vm.$apollo.queries.currentSubscription.refetch).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$apollo.queries.pastLicenseHistoryEntries.refetch).toHaveBeenCalledTimes(
1,
......@@ -333,7 +339,7 @@ describe('SubscriptionManagementApp', () => {
});
});
it('does not the activation success notification', () => {
it('does not show the activation success notification', () => {
expect(findSubscriptionActivationSuccessAlert().exists()).toBe(false);
});
......
import { GlSprintf } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import SubscriptionActivationCard from 'ee/admin/subscriptions/show/components/subscription_activation_card.vue';
import SubscriptionDetailsHistory from 'ee/admin/subscriptions/show/components/subscription_details_history.vue';
import NoActiveSubscription from 'ee_else_ce/admin/subscriptions/show/components/no_active_subscription.vue';
import { isInFuture } from '~/lib/utils/datetime/date_calculation_utility';
import {
instanceHasFutureLicenseBanner,
noActiveSubscription,
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
} from 'ee/admin/subscriptions/show/constants';
import { useFakeDate } from 'helpers/fake_date';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { sprintf } from '~/locale';
import { license, subscriptionFutureHistory, subscriptionPastHistory } from '../mock_data';
Vue.use(VueApollo);
describe('NoActiveSubscription', () => {
// March 16th, 2020
useFakeDate(2021, 2, 16);
let wrapper;
const findActivateSubscriptionCard = () => wrapper.findComponent(SubscriptionActivationCard);
const findSubscriptionDetailsHistory = () => wrapper.findComponent(SubscriptionDetailsHistory);
const findSubscriptionActivationTitle = () =>
wrapper.findByTestId('subscription-activation-title');
const findSubscriptionFutureLicensesAlert = () =>
wrapper.findByTestId('subscription-future-licenses-alert');
const createComponent = (props, listeners) => {
wrapper = shallowMountExtended(NoActiveSubscription, {
propsData: props,
listeners,
stubs: {
GlSprintf,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('without future subscriptions/licenses', () => {
beforeEach(() => {
createComponent({
subscriptionList: subscriptionPastHistory,
});
});
it('shows a title saying there is no active subscription', () => {
expect(findSubscriptionActivationTitle().text()).toBe(noActiveSubscription);
});
it('it shows the past items', () => {
expect(findSubscriptionDetailsHistory().exists()).toBe(true);
expect(findSubscriptionDetailsHistory().props()).toMatchObject({
subscriptionList: subscriptionPastHistory,
});
});
});
describe('Empty', () => {
beforeEach(() => {
createComponent({
subscriptionList: [],
});
});
it('expect empty', () => {
expect(findSubscriptionDetailsHistory().exists()).toBe(false);
});
});
describe('with future subscriptions/licenses', () => {
beforeEach(() => {
createComponent({
subscriptionList: [...subscriptionPastHistory, ...subscriptionFutureHistory],
});
});
it('shows the upcoming license notification', () => {
expect(findSubscriptionFutureLicensesAlert().exists()).toBe(true);
});
it('shows the upcoming license date in the notification', () => {
// Getting the next future dated license start date
const nextLicenseStartDate = [...subscriptionPastHistory, ...subscriptionFutureHistory]
.filter(({ startsAt }) => isInFuture(new Date(startsAt)))
.sort((a, b) => Date(a) - Date(b))
.pop().startsAt;
const expectedText = sprintf(instanceHasFutureLicenseBanner.message, {
date: nextLicenseStartDate,
});
expect(findSubscriptionFutureLicensesAlert().text()).toBe(expectedText);
});
it('shows the upcoming licenses', () => {
expect(findSubscriptionDetailsHistory().exists()).toBe(true);
expect(findSubscriptionDetailsHistory().props()).toMatchObject({
subscriptionList: [...subscriptionPastHistory, ...subscriptionFutureHistory],
});
});
});
describe('Activation form', () => {
let onSuccess;
beforeEach(() => {
onSuccess = jest.fn();
createComponent(
{
subscriptionList: [],
},
{
[SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT]: onSuccess,
},
);
});
it('shows the subscription activation form', () => {
expect(findActivateSubscriptionCard().exists()).toBe(true);
});
it('passes activation card events', async () => {
findActivateSubscriptionCard().vm.$emit(
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
license.ULTIMATE,
);
await nextTick();
expect(onSuccess).toHaveBeenCalledWith(license.ULTIMATE);
});
});
});
......@@ -35570,6 +35570,12 @@ msgstr ""
msgid "SuperSonics|You do not have an active subscription"
msgstr ""
msgid "SuperSonics|You have a future dated license"
msgstr ""
msgid "SuperSonics|You have added a license that activates on %{date}. Please see the subscription history table below for more details."
msgstr ""
msgid "SuperSonics|You have successfully added a license that activates on %{date}. Please see the subscription history table below for more details."
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