Commit ad7f40bc authored by mlunoe's avatar mlunoe

Feat(SM: Subscription History): add future subscr

Add future subscriptions to the subscription
history table for self-managed users

Changelog: added
EE: true
parent 965fd314
...@@ -11,13 +11,15 @@ import { ...@@ -11,13 +11,15 @@ import {
subscriptionHistoryFailedTitle, subscriptionHistoryFailedTitle,
subscriptionHistoryFailedMessage, subscriptionHistoryFailedMessage,
currentSubscriptionsEntryName, currentSubscriptionsEntryName,
historySubscriptionsEntryName, pastSubscriptionsEntryName,
futureSubscriptionsEntryName,
subscriptionMainTitle, subscriptionMainTitle,
exportLicenseUsageBtnText, exportLicenseUsageBtnText,
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT, SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
} from '../constants'; } from '../constants';
import getCurrentLicense from '../graphql/queries/get_current_license.query.graphql'; import getCurrentLicense from '../graphql/queries/get_current_license.query.graphql';
import getLicenseHistory from '../graphql/queries/get_license_history.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 SubscriptionActivationCard from './subscription_activation_card.vue';
import SubscriptionBreakdown from './subscription_breakdown.vue'; import SubscriptionBreakdown from './subscription_breakdown.vue';
import SubscriptionPurchaseCard from './subscription_purchase_card.vue'; import SubscriptionPurchaseCard from './subscription_purchase_card.vue';
...@@ -63,21 +65,31 @@ export default { ...@@ -63,21 +65,31 @@ export default {
this.subscriptionFetchError = currentSubscriptionsEntryName; this.subscriptionFetchError = currentSubscriptionsEntryName;
}, },
}, },
subscriptionHistory: { pastLicenseHistoryEntries: {
query: getLicenseHistory, query: getPastLicenseHistory,
update({ licenseHistoryEntries }) { update({ licenseHistoryEntries }) {
return licenseHistoryEntries?.nodes || []; return licenseHistoryEntries?.nodes || [];
}, },
error() { error() {
this.subscriptionFetchError = historySubscriptionsEntryName; this.subscriptionFetchError = pastSubscriptionsEntryName;
},
},
futureLicenseHistoryEntries: {
query: getFutureLicenseHistory,
update({ subscriptionFutureEntries }) {
return subscriptionFutureEntries?.nodes || [];
},
error() {
this.subscriptionFetchError = futureSubscriptionsEntryName;
}, },
}, },
}, },
data() { data() {
return { return {
currentSubscription: {}, currentSubscription: {},
pastLicenseHistoryEntries: [],
futureLicenseHistoryEntries: [],
activationNotification: null, activationNotification: null,
subscriptionHistory: [],
subscriptionFetchError: null, subscriptionFetchError: null,
}; };
}, },
...@@ -88,6 +100,9 @@ export default { ...@@ -88,6 +100,9 @@ export default {
canShowSubscriptionDetails() { canShowSubscriptionDetails() {
return this.hasActiveLicense || this.hasValidSubscriptionData; return this.hasActiveLicense || this.hasValidSubscriptionData;
}, },
subscriptionHistory() {
return [...this.futureLicenseHistoryEntries, ...this.pastLicenseHistoryEntries];
},
}, },
created() { created() {
this.$options.activationListeners = { this.$options.activationListeners = {
......
...@@ -64,6 +64,13 @@ export default { ...@@ -64,6 +64,13 @@ export default {
}, },
{ {
key: 'activatedAt', key: 'activatedAt',
formatter: (v, k, { activatedAt }) => {
if (!activatedAt) {
return '-';
}
return activatedAt;
},
label: subscriptionTable.activatedAt, label: subscriptionTable.activatedAt,
tdAttr, tdAttr,
tdClass: this.cellClass, tdClass: this.cellClass,
...@@ -109,11 +116,9 @@ export default { ...@@ -109,11 +116,9 @@ export default {
isCurrentSubscription({ id }) { isCurrentSubscription({ id }) {
return id === this.currentSubscriptionId; return id === this.currentSubscriptionId;
}, },
rowAttr(item) { rowAttr() {
return { return {
'data-testid': this.isCurrentSubscription(item) 'data-testid': 'subscription-history-row',
? 'subscription-current'
: 'subscription-history-row',
}; };
}, },
rowClass(item) { rowClass(item) {
......
...@@ -22,7 +22,8 @@ export const subscriptionHistoryFailedMessage = s__( ...@@ -22,7 +22,8 @@ export const subscriptionHistoryFailedMessage = s__(
'SuperSonics|Your %{subscriptionEntryName} cannot be displayed at the moment. Please refresh the page to try again.', 'SuperSonics|Your %{subscriptionEntryName} cannot be displayed at the moment. Please refresh the page to try again.',
); );
export const currentSubscriptionsEntryName = s__('SuperSonics|current subscription'); export const currentSubscriptionsEntryName = s__('SuperSonics|current subscription');
export const historySubscriptionsEntryName = s__('SuperSonics|history subscriptions'); export const pastSubscriptionsEntryName = s__('SuperSonics|past subscriptions');
export const futureSubscriptionsEntryName = s__('SuperSonics|future subscriptions');
export const cancelLabel = __('Cancel'); export const cancelLabel = __('Cancel');
export const activateLabel = s__('AdminUsers|Activate'); export const activateLabel = s__('AdminUsers|Activate');
......
query getFutureLicenseHistory {
subscriptionFutureEntries {
nodes {
type
plan
name
email
company
usersInLicenseCount
startsAt
expiresAt
}
}
}
import produce from 'immer'; import produce from 'immer';
import getCurrentLicense from './queries/get_current_license.query.graphql'; import getCurrentLicense from './queries/get_current_license.query.graphql';
import getLicenseHistory from './queries/get_license_history.query.graphql'; import getPastLicenseHistory from './queries/get_past_license_history.query.graphql';
export const getLicenseFromData = ({ data } = {}) => data?.gitlabSubscriptionActivate?.license; export const getLicenseFromData = ({ data } = {}) => data?.gitlabSubscriptionActivate?.license;
export const getErrorsAsData = ({ data } = {}) => data?.gitlabSubscriptionActivate?.errors || []; export const getErrorsAsData = ({ data } = {}) => data?.gitlabSubscriptionActivate?.errors || [];
...@@ -14,12 +14,12 @@ export const updateSubscriptionAppCache = (cache, mutation) => { ...@@ -14,12 +14,12 @@ export const updateSubscriptionAppCache = (cache, mutation) => {
draftData.currentLicense = license; draftData.currentLicense = license;
}); });
cache.writeQuery({ query: getCurrentLicense, data }); cache.writeQuery({ query: getCurrentLicense, data });
const subscriptionsList = cache.readQuery({ query: getLicenseHistory }); const pastSubscriptions = cache.readQuery({ query: getPastLicenseHistory });
const subscriptionListData = produce(subscriptionsList, (draftData) => { const pastSubscriptionsData = produce(pastSubscriptions, (draftData) => {
draftData.licenseHistoryEntries.nodes = [ draftData.licenseHistoryEntries.nodes = [
license, license,
...subscriptionsList.licenseHistoryEntries.nodes, ...pastSubscriptions.licenseHistoryEntries.nodes,
]; ];
}); });
cache.writeQuery({ query: getLicenseHistory, data: subscriptionListData }); cache.writeQuery({ query: getPastLicenseHistory, data: pastSubscriptionsData });
}; };
...@@ -27,7 +27,7 @@ import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisse ...@@ -27,7 +27,7 @@ import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisse
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { license, subscriptionHistory } from '../mock_data'; import { license, subscriptionPastHistory, subscriptionFutureHistory } from '../mock_data';
describe('Subscription Breakdown', () => { describe('Subscription Breakdown', () => {
let axiosMock; let axiosMock;
...@@ -35,7 +35,7 @@ describe('Subscription Breakdown', () => { ...@@ -35,7 +35,7 @@ describe('Subscription Breakdown', () => {
let glModalDirective; let glModalDirective;
let userCalloutDismissSpy; let userCalloutDismissSpy;
const [, licenseFile] = subscriptionHistory; const [, licenseFile] = subscriptionPastHistory;
const congratulationSvgPath = '/path/to/svg'; const congratulationSvgPath = '/path/to/svg';
const connectivityHelpURL = 'connectivity/help/url'; const connectivityHelpURL = 'connectivity/help/url';
const customersPortalUrl = 'customers.dot'; const customersPortalUrl = 'customers.dot';
...@@ -88,7 +88,7 @@ describe('Subscription Breakdown', () => { ...@@ -88,7 +88,7 @@ describe('Subscription Breakdown', () => {
}, },
propsData: { propsData: {
subscription: license.ULTIMATE, subscription: license.ULTIMATE,
subscriptionList: subscriptionHistory, subscriptionList: [...subscriptionFutureHistory, ...subscriptionPastHistory],
...props, ...props,
}, },
stubs: { stubs: {
...@@ -398,7 +398,10 @@ describe('Subscription Breakdown', () => { ...@@ -398,7 +398,10 @@ describe('Subscription Breakdown', () => {
it('provides the correct props to the subscription history component', () => { it('provides the correct props to the subscription history component', () => {
expect(findDetailsHistory().props('currentSubscriptionId')).toBe(license.ULTIMATE.id); expect(findDetailsHistory().props('currentSubscriptionId')).toBe(license.ULTIMATE.id);
expect(findDetailsHistory().props('subscriptionList')).toBe(subscriptionHistory); expect(findDetailsHistory().props('subscriptionList')).toMatchObject([
...subscriptionFutureHistory,
...subscriptionPastHistory,
]);
}); });
}); });
......
import { GlBadge, GlIcon, GlTooltip } from '@gitlab/ui'; import { GlBadge, GlIcon, GlTooltip } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import SubscriptionDetailsHistory from 'ee/admin/subscriptions/show/components/subscription_details_history.vue'; import SubscriptionDetailsHistory from 'ee/admin/subscriptions/show/components/subscription_details_history.vue';
import { detailsLabels, cloudLicenseText } from 'ee/admin/subscriptions/show/constants'; import {
detailsLabels,
cloudLicenseText,
licenseFileText,
subscriptionTypes,
} from 'ee/admin/subscriptions/show/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { license, subscriptionHistory } from '../mock_data'; import { license, subscriptionFutureHistory, subscriptionPastHistory } from '../mock_data';
const subscriptionList = [...subscriptionFutureHistory, ...subscriptionPastHistory];
const currentSubscriptionIndex = subscriptionFutureHistory.length;
describe('Subscription Details History', () => { describe('Subscription Details History', () => {
let wrapper; let wrapper;
const findCurrentRow = () => wrapper.findByTestId('subscription-current');
const findTableRows = () => wrapper.findAllByTestId('subscription-history-row'); const findTableRows = () => wrapper.findAllByTestId('subscription-history-row');
const findCurrentRow = () => findTableRows().at(currentSubscriptionIndex);
const cellFinder = (row) => (testId) => extendedWrapper(row).findByTestId(testId); const cellFinder = (row) => (testId) => extendedWrapper(row).findByTestId(testId);
const containsABadge = (row) => row.findComponent(GlBadge).exists(); const containsABadge = (row) => row.findComponent(GlBadge).exists();
const containsATooltip = (row) => row.findComponent(GlTooltip).exists(); const containsATooltip = (row) => row.findComponent(GlTooltip).exists();
...@@ -20,7 +29,7 @@ describe('Subscription Details History', () => { ...@@ -20,7 +29,7 @@ describe('Subscription Details History', () => {
mount(SubscriptionDetailsHistory, { mount(SubscriptionDetailsHistory, {
propsData: { propsData: {
currentSubscriptionId: license.ULTIMATE.id, currentSubscriptionId: license.ULTIMATE.id,
subscriptionList: subscriptionHistory, subscriptionList,
...props, ...props,
}, },
}), }),
...@@ -41,12 +50,12 @@ describe('Subscription Details History', () => { ...@@ -41,12 +50,12 @@ describe('Subscription Details History', () => {
}); });
it('has the correct number of subscription rows', () => { it('has the correct number of subscription rows', () => {
expect(findTableRows()).toHaveLength(1); expect(findTableRows()).toHaveLength(subscriptionList.length);
}); });
it('has the correct license type', () => { it('has the correct license type', () => {
expect(findCurrentRow().text()).toContain(cloudLicenseText); expect(findCurrentRow().text()).toContain(cloudLicenseText);
expect(findTableRows().at(0).text()).toContain('License file'); expect(findTableRows().at(-1).text()).toContain('License file');
}); });
it('has a badge for the license type', () => { it('has a badge for the license type', () => {
...@@ -73,49 +82,53 @@ describe('Subscription Details History', () => { ...@@ -73,49 +82,53 @@ describe('Subscription Details History', () => {
expect(findTableRows().at(0).classes('gl-text-blue-500')).toBe(false); expect(findTableRows().at(0).classes('gl-text-blue-500')).toBe(false);
}); });
describe('cell data', () => { describe.each(Object.entries(subscriptionList))('cell data index=%#', (index, subscription) => {
let findCellByTestid; let findCellByTestid;
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
findCellByTestid = cellFinder(findCurrentRow()); findCellByTestid = cellFinder(findTableRows().at(index));
}); });
it.each` it.each`
testId | key testId | key
${'starts-at'} | ${'startsAt'} ${'starts-at'} | ${'startsAt'}
${'starts-at'} | ${'startsAt'}
${'expires-at'} | ${'expiresAt'} ${'expires-at'} | ${'expiresAt'}
${'users-in-license-count'} | ${'usersInLicenseCount'} ${'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]); const value = subscription[key] || '-';
expect(findCellByTestid(cellTestId).text()).toBe(value);
}); });
it('displays the name field with tooltip', () => { it('displays the name field with tooltip', () => {
const cellTestId = 'subscription-cell-name'; const cellTestId = 'subscription-cell-name';
const text = findCellByTestid(cellTestId).text(); const text = findCellByTestid(cellTestId).text();
expect(text).toContain(subscriptionHistory[0].name); expect(text).toContain(subscription.name);
expect(text).toContain(`(${subscriptionHistory[0].company})`); expect(text).toContain(`(${subscription.company})`);
expect(text).toContain(subscriptionHistory[0].email); expect(text).toContain(subscription.email);
}); });
it('displays sr-only element for screen readers', () => { it('displays sr-only element for screen readers', () => {
const testId = 'subscription-history-sr-only'; const testId = 'subscription-history-sr-only';
const text = findCellByTestid(testId).text(); const text = findCellByTestid(testId).text();
expect(text).not.toContain(subscriptionHistory[0].name); expect(text).not.toContain(subscription.name);
expect(text).toContain(`(${detailsLabels.company}: ${subscriptionHistory[0].company})`); expect(text).toContain(`(${detailsLabels.company}: ${subscription.company})`);
expect(text).toContain(`${detailsLabels.email}: ${subscriptionHistory[0].email}`); expect(text).toContain(`${detailsLabels.email}: ${subscription.email}`);
}); });
it('displays the correct value for the type cell', () => { it('displays the correct value for the type cell', () => {
const cellTestId = `subscription-cell-type`; const cellTestId = `subscription-cell-type`;
expect(findCellByTestid(cellTestId).text()).toBe(cloudLicenseText); const type =
subscription.type === subscriptionTypes.LICENSE_FILE ? licenseFileText : cloudLicenseText;
expect(findCellByTestid(cellTestId).text()).toBe(type);
}); });
it('displays the correct value for the plan cell', () => { it('displays the correct value for the plan cell', () => {
const cellTestId = `subscription-cell-plan`; const cellTestId = `subscription-cell-plan`;
expect(findCellByTestid(cellTestId).text()).toBe('Ultimate'); expect(findCellByTestid(cellTestId).text()).toBe(
capitalizeFirstCharacter(subscription.plan),
);
}); });
}); });
}); });
......
...@@ -35,7 +35,7 @@ export const license = { ...@@ -35,7 +35,7 @@ export const license = {
}, },
}; };
export const subscriptionHistory = [ export const subscriptionPastHistory = [
{ {
activatedAt: '2022-03-16', activatedAt: '2022-03-16',
company: 'ACME Corp', company: 'ACME Corp',
...@@ -62,6 +62,29 @@ export const subscriptionHistory = [ ...@@ -62,6 +62,29 @@ export const subscriptionHistory = [
}, },
]; ];
export const subscriptionFutureHistory = [
{
company: 'ACME Corp',
email: 'user@acmecorp.com',
expiresAt: '2023-03-16',
name: 'Jane Doe',
plan: 'ultimate',
startsAt: '2022-03-11',
type: subscriptionTypes.CLOUD,
usersInLicenseCount: '15',
},
{
company: 'ACME Corp',
email: 'user@acmecorp.com',
expiresAt: '2022-03-16',
name: 'Jane Doe',
plan: 'ultimate',
startsAt: '2021-03-16',
type: subscriptionTypes.CLOUD,
usersInLicenseCount: '10',
},
];
export const activateLicenseMutationResponse = { export const activateLicenseMutationResponse = {
FAILURE: [ FAILURE: [
{ {
......
...@@ -34954,7 +34954,10 @@ msgstr "" ...@@ -34954,7 +34954,10 @@ msgstr ""
msgid "SuperSonics|current subscription" msgid "SuperSonics|current subscription"
msgstr "" msgstr ""
msgid "SuperSonics|history subscriptions" msgid "SuperSonics|future subscriptions"
msgstr ""
msgid "SuperSonics|past subscriptions"
msgstr "" msgstr ""
msgid "Support" msgid "Support"
......
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