Commit 73f4067a authored by Angelo Gulina's avatar Angelo Gulina Committed by Savas Vedova

Subscription Details: integrate GraphQL

parent 9f545b52
<script> <script>
import { s__, sprintf } from '~/locale'; import {
subscriptionActivationTitle,
subscriptionHistoryQueries,
subscriptionMainTitle,
subscriptionQueries,
} from '../constants';
import CloudLicenseSubscriptionActivationForm from './subscription_activation_form.vue'; import CloudLicenseSubscriptionActivationForm from './subscription_activation_form.vue';
import SubscriptionBreakdown from './subscription_breakdown.vue';
export default { export default {
name: 'CloudLicenseApp', name: 'CloudLicenseApp',
components: { components: {
SubscriptionBreakdown,
CloudLicenseSubscriptionActivationForm, CloudLicenseSubscriptionActivationForm,
}, },
i18n: { i18n: {
mainTitle: s__(`CloudLicense|This instance is currently using the %{planName} plan.`), subscriptionActivationTitle,
subscriptionMainTitle,
}, },
inject: ['planName'],
props: { props: {
subscription: { hasActiveLicense: {
type: Boolean,
required: false, required: false,
type: Object, default: false,
default: null, },
},
apollo: {
currentSubscription: {
query: subscriptionQueries.query,
update({ currentLicense }) {
return currentLicense;
},
skip() {
return !this.hasCurrentLicense;
},
},
subscriptionHistory: {
query: subscriptionHistoryQueries.query,
update({ licenseHistoryEntries }) {
return licenseHistoryEntries.nodes || [];
},
}, },
}, },
data() { data() {
return { return {
subscriptionData: this.subscription, currentSubscription: {},
subscriptionHistory: [],
hasCurrentLicense: this.hasActiveLicense,
}; };
}, },
computed: { methods: {
mainTitle() { handleActivation(result) {
return sprintf(this.$options.i18n.mainTitle, { this.hasCurrentLicense = result;
planName: this.planName,
});
}, },
}, },
}; };
...@@ -35,13 +59,20 @@ export default { ...@@ -35,13 +59,20 @@ export default {
<template> <template>
<div class="gl-display-flex gl-justify-content-center gl-flex-direction-column"> <div class="gl-display-flex gl-justify-content-center gl-flex-direction-column">
<h4>{{ s__('CloudLicense|Your subscription') }}</h4> <h4 data-testid="subscription-main-title">{{ $options.i18n.subscriptionMainTitle }}</h4>
<hr /> <hr />
<div class="row"> <div v-if="!hasCurrentLicense" class="row">
<div class="col-12 col-lg-8 offset-lg-2"> <div class="col-12 col-lg-8 offset-lg-2">
<h3 class="gl-mb-7 gl-mt-6 gl-text-center">{{ mainTitle }}</h3> <h3 class="gl-mb-7 gl-mt-6 gl-text-center" data-testid="subscription-activation-title">
<cloud-license-subscription-activation-form v-if="!subscriptionData" /> {{ $options.i18n.subscriptionActivationTitle }}
</h3>
<cloud-license-subscription-activation-form @subscription-activation="handleActivation" />
</div> </div>
</div> </div>
<subscription-breakdown
v-else
:subscription="currentSubscription"
:subscription-list="subscriptionHistory"
/>
</div> </div>
</template> </template>
...@@ -9,7 +9,7 @@ import { ...@@ -9,7 +9,7 @@ import {
GlLink, GlLink,
GlSprintf, GlSprintf,
} from '@gitlab/ui'; } from '@gitlab/ui';
import activateSubscriptionMutation from 'ee/pages/admin/cloud_licenses/graphql/mutations/activate_subscription.mutation.graphql'; import { subscriptionQueries } from '../constants';
export const SUBSCRIPTION_ACTIVATION_EVENT = 'subscription-activation'; export const SUBSCRIPTION_ACTIVATION_EVENT = 'subscription-activation';
...@@ -42,7 +42,7 @@ export default { ...@@ -42,7 +42,7 @@ export default {
this.isLoading = true; this.isLoading = true;
this.$apollo this.$apollo
.mutate({ .mutate({
mutation: activateSubscriptionMutation, mutation: subscriptionQueries.mutation,
variables: { variables: {
gitlabSubscriptionActivateInput: { gitlabSubscriptionActivateInput: {
activationCode: this.activationCode, activationCode: this.activationCode,
...@@ -58,7 +58,7 @@ export default { ...@@ -58,7 +58,7 @@ export default {
if (errors.length) { if (errors.length) {
throw new Error(); throw new Error();
} }
this.$emit(SUBSCRIPTION_ACTIVATION_EVENT, this.activationCode); this.$emit(SUBSCRIPTION_ACTIVATION_EVENT, true);
}, },
) )
.catch(() => { .catch(() => {
...@@ -112,9 +112,9 @@ export default { ...@@ -112,9 +112,9 @@ export default {
" "
> >
<template #link="{ content }"> <template #link="{ content }">
<gl-link href="https://about.gitlab.com/terms/" target="_blank">{{ <gl-link href="https://about.gitlab.com/terms/" target="_blank"
content >{{ content }}
}}</gl-link> </gl-link>
</template> </template>
</gl-sprintf> </gl-sprintf>
</gl-form-checkbox> </gl-form-checkbox>
......
...@@ -10,7 +10,7 @@ import SubscriptionDetailsCard from './subscription_details_card.vue'; ...@@ -10,7 +10,7 @@ import SubscriptionDetailsCard from './subscription_details_card.vue';
import SubscriptionDetailsHistory from './subscription_details_history.vue'; import SubscriptionDetailsHistory from './subscription_details_history.vue';
import SubscriptionDetailsUserInfo from './subscription_details_user_info.vue'; import SubscriptionDetailsUserInfo from './subscription_details_user_info.vue';
export const subscriptionDetailsFields = ['id', 'plan', 'lastSync', 'startsAt', 'renews']; export const subscriptionDetailsFields = ['id', 'plan', 'expiresAt', 'lastSync', 'startsAt'];
export const licensedToFields = ['name', 'email', 'company']; export const licensedToFields = ['name', 'email', 'company'];
export default { export default {
...@@ -44,14 +44,17 @@ export default { ...@@ -44,14 +44,17 @@ export default {
}; };
}, },
computed: { computed: {
canMangeSubscription() {
return false;
},
hasSubscription() { hasSubscription() {
return Boolean(Object.keys(this.subscription).length); return Boolean(Object.keys(this.subscription).length);
}, },
hasSubscriptionHistory() { hasSubscriptionHistory() {
return Boolean(this.subscriptionList.length); return Boolean(this.subscriptionList.length);
}, },
canMangeSubscription() { subscriptionHistory() {
return false; return this.hasSubscriptionHistory ? this.subscriptionList : [this.subscription];
}, },
}, },
}; };
...@@ -87,9 +90,9 @@ export default { ...@@ -87,9 +90,9 @@ export default {
</section> </section>
<subscription-details-user-info v-if="hasSubscription" :subscription="subscription" /> <subscription-details-user-info v-if="hasSubscription" :subscription="subscription" />
<subscription-details-history <subscription-details-history
v-if="hasSubscriptionHistory" v-if="hasSubscription"
:current-subscription-id="subscription.id" :current-subscription-id="subscription.id"
:subscription-list="subscriptionList" :subscription-list="subscriptionHistory"
/> />
</div> </div>
</template> </template>
<script> <script>
import { GlCard } from '@gitlab/ui'; import { GlCard } from '@gitlab/ui';
import { identity } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { detailsLabels } from '../constants'; import { detailsLabels } from '../constants';
import SubscriptionDetailsTable from './subscription_details_table.vue'; import SubscriptionDetailsTable from './subscription_details_table.vue';
const humanReadableDate = (value) => (value ? formatDate(value, 'd mmmm yyyy') : '');
const subscriptionDetailsFormatRules = {
id: getIdFromGraphQLId,
expiresAt: getTimeago().format,
lastSync: getTimeago().format,
plan: capitalizeFirstCharacter,
startsAt: humanReadableDate,
};
export default { export default {
name: 'SubscriptionDetailsCard', name: 'SubscriptionDetailsCard',
components: { components: {
...@@ -26,11 +40,13 @@ export default { ...@@ -26,11 +40,13 @@ export default {
}, },
computed: { computed: {
details() { details() {
return this.detailsFields.map((detail) => ({ return this.detailsFields.map((detail) => {
canCopy: detail === 'id', const label = detailsLabels[detail];
label: detailsLabels[detail], const formatter = subscriptionDetailsFormatRules[detail] || identity;
value: this.subscription[detail], const valueToFormat = this.subscription[detail];
})); const value = valueToFormat ? formatter(valueToFormat) : '';
return { canCopy: detail === 'id', label, value };
});
}, },
}, },
}; };
......
...@@ -59,21 +59,22 @@ export default { ...@@ -59,21 +59,22 @@ export default {
}, },
{ {
key: 'plan', key: 'plan',
formatter: (v, k, item) => capitalizeFirstCharacter(item.plan),
label: detailsLabels.plan, label: detailsLabels.plan,
tdAttr, tdAttr,
tdClass: this.cellClass, tdClass: this.cellClass,
thClass, thClass,
}, },
{ {
key: 'startsAt', key: 'activatedAt',
label: subscriptionTable.activatedOn, label: subscriptionTable.activatedAt,
tdAttr, tdAttr,
tdClass: this.cellClass, tdClass: this.cellClass,
thClass, thClass,
}, },
{ {
key: 'validFrom', key: 'startsAt',
label: subscriptionTable.validFrom, label: subscriptionTable.startsAt,
tdAttr, tdAttr,
tdClass: this.cellClass, tdClass: this.cellClass,
thClass, thClass,
...@@ -86,7 +87,7 @@ export default { ...@@ -86,7 +87,7 @@ export default {
thClass, thClass,
}, },
{ {
key: 'usersInLicense', key: 'usersInLicenseCount',
label: subscriptionTable.seats, label: subscriptionTable.seats,
tdAttr, tdAttr,
tdClass: this.cellClass, tdClass: this.cellClass,
...@@ -94,6 +95,8 @@ export default { ...@@ -94,6 +95,8 @@ export default {
}, },
{ {
key: 'type', key: 'type',
formatter: (v, k, item) =>
sprintf(subscriptionTypeText, { type: capitalizeFirstCharacter(item.type) }),
label: subscriptionTable.type, label: subscriptionTable.type,
tdAttr, tdAttr,
tdClass: this.cellClass, tdClass: this.cellClass,
...@@ -106,9 +109,6 @@ export default { ...@@ -106,9 +109,6 @@ export default {
cellClass(_, x, item) { cellClass(_, x, item) {
return item.id === this.currentSubscriptionId ? tdClassHighlight : tdClassBase; return item.id === this.currentSubscriptionId ? tdClassHighlight : tdClassBase;
}, },
getType(type) {
return sprintf(subscriptionTypeText, { type: capitalizeFirstCharacter(type) });
},
rowAttr() { rowAttr() {
return { return {
'data-testid': 'subscription-history-row', 'data-testid': 'subscription-history-row',
...@@ -135,8 +135,8 @@ export default { ...@@ -135,8 +135,8 @@ export default {
responsive responsive
stacked="sm" stacked="sm"
> >
<template #cell(type)="{ item }"> <template #cell(type)="{ value }">
<gl-badge size="md" variant="info">{{ getType(item.type) }}</gl-badge> <gl-badge size="md" variant="info">{{ value }}</gl-badge>
</template> </template>
</gl-table> </gl-table>
</section> </section>
......
...@@ -21,6 +21,7 @@ export default { ...@@ -21,6 +21,7 @@ export default {
}, },
{ {
key: 'value', key: 'value',
formatter: (v, k, item) => item.value.toString(),
label: '', label: '',
thClass: DEFAULT_TH_CLASSES, thClass: DEFAULT_TH_CLASSES,
tdClass: DEFAULT_TD_CLASSES, tdClass: DEFAULT_TD_CLASSES,
...@@ -71,12 +72,12 @@ export default { ...@@ -71,12 +72,12 @@ export default {
</p> </p>
</template> </template>
<template #cell(value)="{ item }"> <template #cell(value)="{ item, value }">
<p class="gl-relative" data-testid="details-content"> <p class="gl-relative" data-testid="details-content">
{{ item.value }} {{ value || '-' }}
<clipboard-button <clipboard-button
v-if="item.canCopy" v-if="item.canCopy"
:text="item.value" :text="value"
:title="$options.i18n.copySubscriptionIdButtonText" :title="$options.i18n.copySubscriptionIdButtonText"
category="tertiary" category="tertiary"
class="gl-absolute gl-mt-n2 gl-ml-2" class="gl-absolute gl-mt-n2 gl-ml-2"
......
...@@ -12,7 +12,7 @@ import { ...@@ -12,7 +12,7 @@ import {
usersOverSubscriptionTitle, usersOverSubscriptionTitle,
} from '../constants'; } from '../constants';
export const billableUsersURL = helpPagePath('subscriptions/self_managed/index'); export const billableUsersURL = helpPagePath('licenses/self_managed/index');
export const trueUpURL = 'https://about.gitlab.com/license-faq/'; export const trueUpURL = 'https://about.gitlab.com/license-faq/';
export default { export default {
...@@ -44,16 +44,16 @@ export default { ...@@ -44,16 +44,16 @@ export default {
}, },
computed: { computed: {
usersInSubscription() { usersInSubscription() {
return this.subscription.usersInLicense; return this.subscription.usersInLicenseCount;
}, },
billableUsers() { billableUsers() {
return this.subscription.billableUsers; return this.subscription.billableUsersCount;
}, },
maximumUsers() { maximumUsers() {
return this.subscription.maximumUsers; return this.subscription.maximumUserCount;
}, },
usersOverSubscription() { usersOverSubscription() {
return this.subscription.usersOverSubscription; return this.subscription.usersOverLicenseCount;
}, },
}, },
}; };
...@@ -110,7 +110,7 @@ export default { ...@@ -110,7 +110,7 @@ export default {
</div> </div>
<div class="col-md-6 gl-mb-5"> <div class="col-md-6 gl-mb-5">
<gl-card data-testid="users-over-subscription"> <gl-card data-testid="users-over-license">
<header> <header>
<h2>{{ usersOverSubscription }}</h2> <h2>{{ usersOverSubscription }}</h2>
<h5 class="gl-font-weight-normal text-uppercase"> <h5 class="gl-font-weight-normal text-uppercase">
......
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import activateSubscriptionMutation from './graphql/mutations/activate_subscription.mutation.graphql';
import getCurrentLicense from './graphql/queries/get_current_license.query.graphql';
import getLicenseHistory from './graphql/queries/get_license_history.query.graphql';
export const subscriptionMainTitle = s__('SuperSonics|Your subscription');
export const subscriptionActivationTitle = s__(
`SuperSonics|You do not have an active subscription`,
);
export const subscriptionDetailsHeaderText = s__('SuperSonics|Subscription details'); export const subscriptionDetailsHeaderText = s__('SuperSonics|Subscription details');
export const licensedToHeaderText = s__('SuperSonics|Licensed to'); export const licensedToHeaderText = s__('SuperSonics|Licensed to');
export const manageSubscriptionButtonText = s__('SuperSonics|Manage'); export const manageSubscriptionButtonText = s__('SuperSonics|Manage');
...@@ -8,13 +15,13 @@ export const copySubscriptionIdButtonText = __('Copy'); ...@@ -8,13 +15,13 @@ export const copySubscriptionIdButtonText = __('Copy');
export const subscriptionTypeText = __('%{type} License'); export const subscriptionTypeText = __('%{type} License');
export const detailsLabels = { export const detailsLabels = {
address: __('Address'), address: __('Address'),
id: s__('SuperSonics|ID'),
company: __('Company'), company: __('Company'),
email: __('Email'), email: __('Email'),
id: s__('SuperSonics|ID'),
lastSync: s__('SuperSonics|Last Sync'), lastSync: s__('SuperSonics|Last Sync'),
name: __('Name'), name: __('Name'),
plan: s__('SuperSonics|Plan'), plan: s__('SuperSonics|Plan'),
renews: s__('SuperSonics|Renews'), expiresAt: s__('SuperSonics|Renews'),
startsAt: s__('SuperSonics|Started'), startsAt: s__('SuperSonics|Started'),
}; };
...@@ -35,14 +42,23 @@ export const usersOverSubscriptionText = s__( ...@@ -35,14 +42,23 @@ export const usersOverSubscriptionText = s__(
`CloudLicense|You'll be charged for %{trueUpLinkStart}users over license%{trueUpLinkEnd} on a quarterly or annual basis, depending on the terms of your agreement.`, `CloudLicense|You'll be charged for %{trueUpLinkStart}users over license%{trueUpLinkEnd} on a quarterly or annual basis, depending on the terms of your agreement.`,
); );
export const subscriptionTable = { export const subscriptionTable = {
activatedOn: s__('SuperSonics|Activated on'), activatedAt: s__('SuperSonics|Activated on'),
expiresOn: s__('SuperSonics|Expires on'), expiresOn: s__('SuperSonics|Expires on'),
seats: s__('SuperSonics|Seats'), seats: s__('SuperSonics|Seats'),
startsAt: s__('SuperSonics|Valid From'),
title: __('Subscription History'), title: __('Subscription History'),
type: s__('SuperSonics|Type'), type: s__('SuperSonics|Type'),
validFrom: s__('SuperSonics|Valid From'),
}; };
export const subscriptionType = { export const subscriptionType = {
CLOUD: 'cloud', CLOUD: 'cloud',
LEGACY: 'legacy', LEGACY: 'legacy',
}; };
export const subscriptionQueries = {
query: getCurrentLicense,
mutation: activateSubscriptionMutation,
};
export const subscriptionHistoryQueries = {
query: getLicenseHistory,
};
query getCurrentLicense {
currentLicense {
id
type
plan
name
email
company
startsAt
expiresAt
activatedAt
lastSync
usersInLicenseCount
billableUsersCount
maximumUserCount
usersOverLicenseCount
}
}
query {
licenseHistoryEntries {
nodes {
id
type
plan
name
email
company
usersInLicenseCount
startsAt
expiresAt
activatedAt
}
}
}
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import CloudLicenseShowApp from '../components/app.vue'; import CloudLicenseShowApp from '../components/app.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -16,14 +17,16 @@ export default () => { ...@@ -16,14 +17,16 @@ export default () => {
return null; return null;
} }
const { planName } = el.dataset; const { hasActiveLicense } = el.dataset;
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
provide: { render: (h) =>
planName, h(CloudLicenseShowApp, {
props: {
hasActiveLicense: parseBoolean(hasActiveLicense),
}, },
render: (h) => h(CloudLicenseShowApp), }),
}); });
}; };
...@@ -16,6 +16,10 @@ module LicenseHelper ...@@ -16,6 +16,10 @@ module LicenseHelper
License.current&.plan&.titleize || 'Core' License.current&.plan&.titleize || 'Core'
end end
def has_active_license?
License.current.present?
end
def new_trial_url def new_trial_url
return_to_url = CGI.escape(Gitlab.config.gitlab.url) return_to_url = CGI.escape(Gitlab.config.gitlab.url)
uri = URI.parse(::EE::SUBSCRIPTIONS_URL) uri = URI.parse(::EE::SUBSCRIPTIONS_URL)
...@@ -50,7 +54,7 @@ module LicenseHelper ...@@ -50,7 +54,7 @@ module LicenseHelper
def cloud_license_view_data def cloud_license_view_data
{ {
plan_name: current_license_title has_active_license: (has_active_license? ? 'true' : 'false')
} }
end end
......
...@@ -16,11 +16,11 @@ RSpec.describe "Admin views Cloud License", :js do ...@@ -16,11 +16,11 @@ RSpec.describe "Admin views Cloud License", :js do
context "#{plan} license" do context "#{plan} license" do
let_it_be(:license) { build(:license, plan: plan) } let_it_be(:license) { build(:license, plan: plan) }
it 'displays the correct license name' do it 'displays the subscription details' do
visit(admin_cloud_license_path) visit(admin_cloud_license_path)
page.within(find('#content-body', match: :first)) do page.within(find('#content-body', match: :first)) do
expect(page).to have_content("This instance is currently using the #{plan.titleize} plan.") expect(page).to have_content("Subscription details")
end end
end end
end end
...@@ -33,9 +33,9 @@ RSpec.describe "Admin views Cloud License", :js do ...@@ -33,9 +33,9 @@ RSpec.describe "Admin views Cloud License", :js do
visit(admin_cloud_license_path) visit(admin_cloud_license_path)
end end
it "displays the fallback license name" do it "displays a message signaling there is not active subscription" do
page.within(find('#content-body', match: :first)) do page.within(find('#content-body', match: :first)) do
expect(page).to have_content("This instance is currently using the Core plan.") expect(page).to have_content("You do not have an active subscription")
end end
end end
end end
......
import { shallowMount } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import CloudLicenseApp from 'ee/pages/admin/cloud_licenses/components/app.vue'; import CloudLicenseApp from 'ee/pages/admin/cloud_licenses/components/app.vue';
import CloudLicenseSubscriptionActivationForm from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue'; import SubscriptionActivationForm from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue';
import SubscriptionBreakdown from 'ee/pages/admin/cloud_licenses/components/subscription_breakdown.vue';
import {
subscriptionActivationTitle,
subscriptionHistoryQueries,
subscriptionMainTitle,
subscriptionQueries,
} from 'ee/pages/admin/cloud_licenses/constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { license, subscriptionHistory } from '../mock_data';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('CloudLicenseApp', () => { describe('CloudLicenseApp', () => {
let wrapper; let wrapper;
const findActivateSubscriptionForm = () => const findActivateSubscriptionForm = () => wrapper.findComponent(SubscriptionActivationForm);
wrapper.findComponent(CloudLicenseSubscriptionActivationForm); const findSubscriptionBreakdown = () => wrapper.findComponent(SubscriptionBreakdown);
const findSubscriptionActivationTitle = () =>
wrapper.findByTestId('subscription-activation-title');
const findSubscriptionMainTitle = () => wrapper.findByTestId('subscription-main-title');
let currentSubscriptionResolver;
let subscriptionHistoryResolver;
const createMockApolloProvider = ([subscriptionResolver, historyResolver]) => {
localVue.use(VueApollo);
return createMockApollo([
[subscriptionQueries.query, subscriptionResolver],
[subscriptionHistoryQueries.query, historyResolver],
]);
};
const createComponent = (props = {}) => { const createComponent = (props = {}, resolverMock) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(CloudLicenseApp, { shallowMount(CloudLicenseApp, {
localVue,
apolloProvider: createMockApolloProvider(resolverMock),
propsData: { propsData: {
...props, ...props,
}, },
provide: {
planName: 'Core',
},
}), }),
); );
}; };
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
currentSubscriptionResolver.mockRestore();
subscriptionHistoryResolver.mockRestore();
}); });
describe('Subscription Activation Form', () => { describe('Subscription Activation Form', () => {
beforeEach(() => createComponent()); beforeEach(() => {
currentSubscriptionResolver = jest
.fn()
.mockResolvedValue({ data: { currentLicense: license.ULTIMATE } });
subscriptionHistoryResolver = jest
.fn()
.mockResolvedValue({ data: { licenseHistoryEntries: { nodes: subscriptionHistory } } });
createComponent({}, [currentSubscriptionResolver, subscriptionHistoryResolver]);
});
it('shows the main title', () => {
expect(findSubscriptionMainTitle().text()).toBe(subscriptionMainTitle);
});
describe('without an active license', () => {
it('shows a title saying there is no active subscription', () => {
expect(findSubscriptionActivationTitle().text()).toBe(subscriptionActivationTitle);
});
it('does not query for the current license', () => {
expect(currentSubscriptionResolver).toHaveBeenCalledTimes(0);
});
it('presents a form', () => { it('queries for the current history', () => {
expect(subscriptionHistoryResolver).toHaveBeenCalledTimes(1);
});
it('shows the subscription activation form', () => {
expect(findActivateSubscriptionForm().exists()).toBe(true); expect(findActivateSubscriptionForm().exists()).toBe(true);
}); });
});
it('presents a main title with the plan name', () => { describe('with active license', () => {
expect(wrapper.text()).toContain('Core plan'); beforeEach(() => {
currentSubscriptionResolver = jest
.fn()
.mockResolvedValue({ data: { currentLicense: license.ULTIMATE } });
subscriptionHistoryResolver = jest
.fn()
.mockResolvedValue({ data: { licenseHistoryEntries: { nodes: subscriptionHistory } } });
createComponent({ hasActiveLicense: true }, [
currentSubscriptionResolver,
subscriptionHistoryResolver,
]);
});
it('queries for the current license', () => {
expect(currentSubscriptionResolver).toHaveBeenCalledTimes(1);
});
it('queries for the current history', () => {
expect(subscriptionHistoryResolver).toHaveBeenCalledTimes(1);
});
it('passes the correct data to the subscription breakdown', () => {
expect(findSubscriptionBreakdown().props()).toMatchObject({
subscription: license.ULTIMATE,
subscriptionList: subscriptionHistory,
});
});
}); });
}); });
}); });
...@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo'; ...@@ -4,7 +4,7 @@ import VueApollo from 'vue-apollo';
import CloudLicenseSubscriptionActivationForm, { import CloudLicenseSubscriptionActivationForm, {
SUBSCRIPTION_ACTIVATION_EVENT, SUBSCRIPTION_ACTIVATION_EVENT,
} from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue'; } from 'ee/pages/admin/cloud_licenses/components/subscription_activation_form.vue';
import activateSubscriptionMutation from 'ee/pages/admin/cloud_licenses/graphql/mutations/activate_subscription.mutation.graphql'; import { subscriptionQueries } from 'ee/pages/admin/cloud_licenses/constants';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component'; import { stubComponent } from 'helpers/stub_component';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
...@@ -21,7 +21,7 @@ describe('CloudLicenseApp', () => { ...@@ -21,7 +21,7 @@ describe('CloudLicenseApp', () => {
const createMockApolloProvider = (resolverMock) => { const createMockApolloProvider = (resolverMock) => {
localVue.use(VueApollo); localVue.use(VueApollo);
return createMockApollo([[activateSubscriptionMutation, resolverMock]]); return createMockApollo([[subscriptionQueries.mutation, resolverMock]]);
}; };
const findActivateButton = () => wrapper.findByTestId('activate-button'); const findActivateButton = () => wrapper.findByTestId('activate-button');
...@@ -123,7 +123,7 @@ describe('CloudLicenseApp', () => { ...@@ -123,7 +123,7 @@ describe('CloudLicenseApp', () => {
}); });
it('emits a successful event', () => { it('emits a successful event', () => {
expect(wrapper.emitted(SUBSCRIPTION_ACTIVATION_EVENT)).toEqual([[fakeActivationCode]]); expect(wrapper.emitted(SUBSCRIPTION_ACTIVATION_EVENT)).toEqual([[true]]);
}); });
}); });
......
...@@ -91,17 +91,28 @@ describe('Subscription Breakdown', () => { ...@@ -91,17 +91,28 @@ describe('Subscription Breakdown', () => {
}); });
}); });
describe('with empty data', () => { describe('with no subscription data', () => {
it('does not show user info', () => { it('does not show user info', () => {
createComponent({ props: { subscription: {} } }); createComponent({ props: { subscription: {} } });
expect(findDetailsUserInfo().exists()).toBe(false); expect(findDetailsUserInfo().exists()).toBe(false);
}); });
it('does not show subscription history', () => { it('shows the current subscription as the only history item', () => {
createComponent({ props: { subscription: {}, subscriptionList: [] } });
expect(findDetailsUserInfo().exists()).toBe(false);
});
});
describe('with no subscription history data', () => {
it('shows the current subscription as the only history item', () => {
createComponent({ props: { subscriptionList: [] } }); createComponent({ props: { subscriptionList: [] } });
expect(findDetailsHistory().exists()).toBe(false); expect(findDetailsHistory().props('')).toMatchObject({
currentSubscriptionId: license.ULTIMATE.id,
subscriptionList: [license.ULTIMATE],
});
}); });
}); });
}); });
import { GlCard } from '@gitlab/ui'; import { GlCard } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { subscriptionDetailsFields } from 'ee/pages/admin/cloud_licenses/components/subscription_breakdown.vue';
import SubscriptionDetailsCard from 'ee/pages/admin/cloud_licenses/components/subscription_details_card.vue'; import SubscriptionDetailsCard from 'ee/pages/admin/cloud_licenses/components/subscription_details_card.vue';
import SubscriptionDetailsTable from 'ee/pages/admin/cloud_licenses/components/subscription_details_table.vue'; import SubscriptionDetailsTable from 'ee/pages/admin/cloud_licenses/components/subscription_details_table.vue';
import { useFakeDate } from 'helpers/fake_date';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { license } from '../mock_data'; import { license } from '../mock_data';
describe('Subscription Details Card', () => { describe('Subscription Details Card', () => {
// March 16th, 2020
useFakeDate(2021, 2, 16);
let wrapper; let wrapper;
const findCard = () => wrapper.findComponent(GlCard); const findCard = () => wrapper.findComponent(GlCard);
...@@ -14,7 +19,7 @@ describe('Subscription Details Card', () => { ...@@ -14,7 +19,7 @@ describe('Subscription Details Card', () => {
const findSubscriptionDetailsTable = () => wrapper.findComponent(SubscriptionDetailsTable); const findSubscriptionDetailsTable = () => wrapper.findComponent(SubscriptionDetailsTable);
const createComponent = ( const createComponent = (
{ detailsFields = ['id', 'plan'], headerText, subscription = license.ULTIMATE } = {}, { detailsFields = subscriptionDetailsFields, headerText, subscription = license.ULTIMATE } = {},
slots, slots,
) => { ) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
...@@ -56,13 +61,28 @@ describe('Subscription Details Card', () => { ...@@ -56,13 +61,28 @@ describe('Subscription Details Card', () => {
{ {
canCopy: true, canCopy: true,
label: 'ID', label: 'ID',
value: '1309188', value: 13,
}, },
{ {
canCopy: false, canCopy: false,
label: 'Plan', label: 'Plan',
value: 'Ultimate', value: 'Ultimate',
}, },
{
canCopy: false,
label: 'Renews',
value: 'in 1 year',
},
{
canCopy: false,
label: 'Last Sync',
value: 'just now',
},
{
canCopy: false,
label: 'Started',
value: '11 March 2021',
},
]); ]);
}); });
}); });
......
...@@ -66,11 +66,10 @@ describe('Subscription Details History', () => { ...@@ -66,11 +66,10 @@ describe('Subscription Details History', () => {
${'name'} | ${'name'} ${'name'} | ${'name'}
${'email'} | ${'email'} ${'email'} | ${'email'}
${'company'} | ${'company'} ${'company'} | ${'company'}
${'plan'} | ${'plan'}
${'starts-at'} | ${'startsAt'} ${'starts-at'} | ${'startsAt'}
${'valid-from'} | ${'validFrom'} ${'starts-at'} | ${'startsAt'}
${'expires-at'} | ${'expiresAt'} ${'expires-at'} | ${'expiresAt'}
${'users-in-license'} | ${'usersInLicense'} ${'users-in-license-count'} | ${'usersInLicenseCount'}
`('displays the correct value for the $testId cell', ({ testId, key }) => { `('displays the correct value for the $testId cell', ({ testId, key }) => {
const cellTestId = `subscription-cell-${testId}`; const cellTestId = `subscription-cell-${testId}`;
expect(findCellByTestid(cellTestId).text()).toBe(subscriptionHistory[0][key]); expect(findCellByTestid(cellTestId).text()).toBe(subscriptionHistory[0][key]);
...@@ -80,6 +79,11 @@ describe('Subscription Details History', () => { ...@@ -80,6 +79,11 @@ describe('Subscription Details History', () => {
const cellTestId = `subscription-cell-type`; const cellTestId = `subscription-cell-type`;
expect(findCellByTestid(cellTestId).text()).toBe('Cloud License'); expect(findCellByTestid(cellTestId).text()).toBe('Cloud License');
}); });
it('displays the correct value for the plan cell', () => {
const cellTestId = `subscription-cell-plan`;
expect(findCellByTestid(cellTestId).text()).toBe('Ultimate');
});
}); });
}); });
......
...@@ -17,7 +17,7 @@ import { ...@@ -17,7 +17,7 @@ import {
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { license } from '../mock_data'; import { license } from '../mock_data';
describe('Subscription Details Card', () => { describe('Subscription Details User Info', () => {
let wrapper; let wrapper;
const itif = (condition) => (condition ? it : it.skip); const itif = (condition) => (condition ? it : it.skip);
...@@ -46,7 +46,7 @@ describe('Subscription Details Card', () => { ...@@ -46,7 +46,7 @@ describe('Subscription Details Card', () => {
${'users-in-license'} | ${'10'} | ${usersInSubscriptionTitle} | ${usersInSubscriptionText} | ${false} ${'users-in-license'} | ${'10'} | ${usersInSubscriptionTitle} | ${usersInSubscriptionText} | ${false}
${'billable-users'} | ${'8'} | ${billableUsersTitle} | ${billableUsersText} | ${billableUsersURL} ${'billable-users'} | ${'8'} | ${billableUsersTitle} | ${billableUsersText} | ${billableUsersURL}
${'maximum-users'} | ${'8'} | ${maximumUsersTitle} | ${maximumUsersText} | ${false} ${'maximum-users'} | ${'8'} | ${maximumUsersTitle} | ${maximumUsersText} | ${false}
${'users-over-subscription'} | ${'0'} | ${usersOverSubscriptionTitle} | ${usersOverSubscriptionText} | ${trueUpURL} ${'users-over-license'} | ${'0'} | ${usersOverSubscriptionTitle} | ${usersOverSubscriptionText} | ${trueUpURL}
`('with data for $card', ({ testId, info, title, text, link }) => { `('with data for $card', ({ testId, info, title, text, link }) => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
......
...@@ -2,47 +2,47 @@ import { subscriptionType } from 'ee/pages/admin/cloud_licenses/constants'; ...@@ -2,47 +2,47 @@ import { subscriptionType } from 'ee/pages/admin/cloud_licenses/constants';
export const license = { export const license = {
ULTIMATE: { ULTIMATE: {
billableUsers: '8', activatedAt: '2022-03-16',
billableUsersCount: '8',
expiresAt: '2022-03-16',
company: 'ACME Corp', company: 'ACME Corp',
email: 'user@acmecorp.com', email: 'user@acmecorp.com',
id: '1309188', id: 'gid://gitlab/License/13',
lastSync: 'just now - actual date', lastSync: '2021-03-16T00:00:00.000',
maximumUsers: '8', maximumUserCount: '8',
name: 'Jane Doe', name: 'Jane Doe',
plan: 'Ultimate', plan: 'ultimate',
startsAt: '22 February', startsAt: '2021-03-11',
renews: 'in 11 months', type: subscriptionType.CLOUD,
usersInLicense: '10', usersInLicenseCount: '10',
usersOverSubscription: '0', usersOverLicenseCount: '0',
}, },
}; };
export const subscriptionHistory = [ export const subscriptionHistory = [
{ {
activatedAt: '2022-03-16',
company: 'ACME Corp', company: 'ACME Corp',
email: 'user@acmecorp.com', email: 'user@acmecorp.com',
expiresAt: '15-03-2022', expiresAt: '2022-03-16',
// TODO: verify presence in graphQL response id: 'gid://gitlab/License/13',
id: '1309188',
name: 'Jane Doe', name: 'Jane Doe',
plan: 'Ultimate', plan: 'ultimate',
startsAt: '16-03-2021', startsAt: '2021-03-11',
type: subscriptionType.CLOUD, type: subscriptionType.CLOUD,
validFrom: '16-03-2021', usersInLicenseCount: '10',
usersInLicense: '10',
}, },
{ {
activatedAt: '2020-11-05',
company: 'ACME Corp', company: 'ACME Corp',
email: 'user@acmecorp.com', email: 'user@acmecorp.com',
expiresAt: '30-06-2021', expiresAt: '2021-03-16',
// TODO: verify presence in graphQL response id: 'gid://gitlab/License/11',
id: '000000000',
name: 'Jane Doe', name: 'Jane Doe',
plan: 'Ultimate', plan: 'premium',
startsAt: '01-07-2020', startsAt: '2020-03-16',
type: subscriptionType.LEGACY, type: subscriptionType.LEGACY,
validFrom: '01-07-2020', usersInLicenseCount: '5',
usersInLicense: '5',
}, },
]; ];
......
...@@ -86,7 +86,7 @@ RSpec.describe LicenseHelper do ...@@ -86,7 +86,7 @@ RSpec.describe LicenseHelper do
license = double('License', plan: custom_plan) license = double('License', plan: custom_plan)
allow(License).to receive(:current).and_return(license) allow(License).to receive(:current).and_return(license)
expect(cloud_license_view_data).to eq({ plan_name: 'Custom Plan' }) expect(cloud_license_view_data).to eq({ has_active_license: 'true' })
end end
end end
...@@ -94,7 +94,7 @@ RSpec.describe LicenseHelper do ...@@ -94,7 +94,7 @@ RSpec.describe LicenseHelper do
it 'returns the data for the view' do it 'returns the data for the view' do
allow(License).to receive(:current).and_return(nil) allow(License).to receive(:current).and_return(nil)
expect(cloud_license_view_data).to eq({ plan_name: 'Core' }) expect(cloud_license_view_data).to eq({ has_active_license: 'false' })
end end
end end
end end
......
...@@ -6647,9 +6647,6 @@ msgstr "" ...@@ -6647,9 +6647,6 @@ msgstr ""
msgid "CloudLicense|Paste your activation code" msgid "CloudLicense|Paste your activation code"
msgstr "" msgstr ""
msgid "CloudLicense|This instance is currently using the %{planName} plan."
msgstr ""
msgid "CloudLicense|This is the highest peak of users on your installation since the license started." msgid "CloudLicense|This is the highest peak of users on your installation since the license started."
msgstr "" msgstr ""
...@@ -6668,9 +6665,6 @@ msgstr "" ...@@ -6668,9 +6665,6 @@ msgstr ""
msgid "CloudLicense|You'll be charged for %{trueUpLinkStart}users over license%{trueUpLinkEnd} on a quarterly or annual basis, depending on the terms of your agreement." msgid "CloudLicense|You'll be charged for %{trueUpLinkStart}users over license%{trueUpLinkEnd} on a quarterly or annual basis, depending on the terms of your agreement."
msgstr "" msgstr ""
msgid "CloudLicense|Your subscription"
msgstr ""
msgid "Cluster" msgid "Cluster"
msgstr "" msgstr ""
...@@ -30351,6 +30345,12 @@ msgstr "" ...@@ -30351,6 +30345,12 @@ msgstr ""
msgid "SuperSonics|Valid From" msgid "SuperSonics|Valid From"
msgstr "" msgstr ""
msgid "SuperSonics|You do not have an active subscription"
msgstr ""
msgid "SuperSonics|Your subscription"
msgstr ""
msgid "Support" msgid "Support"
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