Commit b40b5a91 authored by mlunoe's avatar mlunoe

Fix(SM: Subscription): Refresh local state

When submitting a new license, refresh the local
state to reflect the changes correctly on the
frontend. The particular issue this fixes is when
the user submits a license that is not the current
license, e.g. a future-dated license.

https://gitlab.com/gitlab-org/gitlab/-/issues/352092

Changelog: added
EE: true
parent f88e9e0b
...@@ -121,6 +121,9 @@ export default { ...@@ -121,6 +121,9 @@ export default {
} else { } else {
this.activationNotification = { title: subscriptionActivationNotificationText }; this.activationNotification = { title: subscriptionActivationNotificationText };
} }
this.$apollo.queries.currentSubscription.refetch();
this.$apollo.queries.pastLicenseHistoryEntries.refetch();
this.$apollo.queries.futureLicenseHistoryEntries.refetch();
}, },
dismissActivationNotification() { dismissActivationNotification() {
this.activationNotification = null; this.activationNotification = null;
......
...@@ -19,7 +19,7 @@ import { ...@@ -19,7 +19,7 @@ import {
SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT, SUBSCRIPTION_ACTIVATION_FINALIZED_EVENT,
subscriptionActivationForm, subscriptionActivationForm,
} from '../constants'; } from '../constants';
import { getErrorsAsData, getLicenseFromData, updateSubscriptionAppCache } from '../graphql/utils'; import { getErrorsAsData, getLicenseFromData } from '../graphql/utils';
import activateSubscriptionMutation from '../graphql/mutations/activate_subscription.mutation.graphql'; import activateSubscriptionMutation from '../graphql/mutations/activate_subscription.mutation.graphql';
const feedbackMap = { const feedbackMap = {
...@@ -124,7 +124,6 @@ export default { ...@@ -124,7 +124,6 @@ export default {
if (license) { if (license) {
this.$emit(SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT, license); this.$emit(SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT, license);
} }
this.updateSubscriptionAppCache(cache, res);
}, },
}) })
.catch((error) => { .catch((error) => {
...@@ -135,7 +134,6 @@ export default { ...@@ -135,7 +134,6 @@ export default {
this.isLoading = false; this.isLoading = false;
}); });
}, },
updateSubscriptionAppCache,
}, },
}; };
</script> </script>
......
import produce from 'immer';
import getCurrentLicense from './queries/get_current_license.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 || [];
export const updateSubscriptionAppCache = (cache, mutation) => {
const license = getLicenseFromData(mutation);
if (!license) {
return;
}
const data = produce({}, (draftData) => {
draftData.currentLicense = license;
});
cache.writeQuery({ query: getCurrentLicense, data });
const pastSubscriptions = cache.readQuery({ query: getPastLicenseHistory });
const pastSubscriptionsData = produce(pastSubscriptions, (draftData) => {
draftData.licenseHistoryEntries.nodes = [
license,
...pastSubscriptions.licenseHistoryEntries.nodes,
];
});
cache.writeQuery({ query: getPastLicenseHistory, data: pastSubscriptionsData });
};
...@@ -33,6 +33,14 @@ RSpec.describe 'Admin views Subscription', :js do ...@@ -33,6 +33,14 @@ RSpec.describe 'Admin views Subscription', :js do
end end
end end
shared_examples 'no active license' do
it 'displays a message signaling there is not active subscription' do
page.within(find('#content-body', match: :first)) do
expect(page).to have_content('You do not have an active subscription')
end
end
end
context 'with a cloud license' do context 'with a cloud license' do
let!(:license) { create_current_license(cloud_licensing_enabled: true, plan: License::ULTIMATE_PLAN) } let!(:license) { create_current_license(cloud_licensing_enabled: true, plan: License::ULTIMATE_PLAN) }
...@@ -119,19 +127,15 @@ RSpec.describe 'Admin views Subscription', :js do ...@@ -119,19 +127,15 @@ RSpec.describe 'Admin views Subscription', :js do
end end
context 'with no active subscription' do context 'with no active subscription' do
let_it_be(:license) { nil } let_it_be(:license_to_be_created) { nil }
before do before do
allow(License).to receive(:current).and_return(license) License.current.destroy!
visit(admin_subscription_path) visit(admin_subscription_path)
end end
it 'displays a message signaling there is not active subscription' do it_behaves_like 'no active license'
page.within(find('#content-body', match: :first)) do
expect(page).to have_content('You do not have an active subscription')
end
end
it 'does not display the Export License Usage File button' do it 'does not display the Export License Usage File button' do
expect(page).not_to have_link('Export license usage file', href: admin_license_usage_export_path(format: :csv)) expect(page).not_to have_link('Export license usage file', href: admin_license_usage_export_path(format: :csv))
...@@ -144,8 +148,9 @@ RSpec.describe 'Admin views Subscription', :js do ...@@ -144,8 +148,9 @@ RSpec.describe 'Admin views Subscription', :js do
"data": { "data": {
"cloudActivationActivate": { "cloudActivationActivate": {
"errors": ["invalid activation code"], "errors": ["invalid activation code"],
"license": nil "license": license_to_be_created
} },
"success": "true"
} }
}.to_json, headers: { 'Content-Type' => 'application/json' }) }.to_json, headers: { 'Content-Type' => 'application/json' })
...@@ -160,9 +165,9 @@ RSpec.describe 'Admin views Subscription', :js do ...@@ -160,9 +165,9 @@ RSpec.describe 'Admin views Subscription', :js do
end end
context 'when activating a future-dated subscription' do context 'when activating a future-dated subscription' do
before do let_it_be(:license_to_be_created) { build(:license, data: build(:gitlab_license, { starts_at: Date.current + 1.month, cloud_licensing_enabled: true, plan: License::ULTIMATE_PLAN }).export) }
license_to_be_created = create(:license, data: create(:gitlab_license, { starts_at: Date.today + 1.month, cloud_licensing_enabled: true, plan: License::ULTIMATE_PLAN }).export)
before do
stub_request(:post, EE::SUBSCRIPTIONS_GRAPHQL_URL) stub_request(:post, EE::SUBSCRIPTIONS_GRAPHQL_URL)
.to_return(status: 200, body: { .to_return(status: 200, body: {
"data": { "data": {
...@@ -180,12 +185,14 @@ RSpec.describe 'Admin views Subscription', :js do ...@@ -180,12 +185,14 @@ RSpec.describe 'Admin views Subscription', :js do
it 'shows a successful future-dated activation message' do it 'shows a successful future-dated activation message' do
expect(page).to have_content('Your future dated license was successfully added') expect(page).to have_content('Your future dated license was successfully added')
end end
it_behaves_like 'no active license'
end end
context 'when activating a new subscription' do context 'when activating a new subscription' do
before do let_it_be(:license_to_be_created) { build(:license, data: build(:gitlab_license, { starts_at: Date.current, cloud_licensing_enabled: true, plan: License::ULTIMATE_PLAN }).export) }
license_to_be_created = create(:license, data: create(:gitlab_license, { starts_at: Date.today, cloud_licensing_enabled: true, plan: License::ULTIMATE_PLAN }).export)
before do
stub_request(:post, EE::SUBSCRIPTIONS_GRAPHQL_URL) stub_request(:post, EE::SUBSCRIPTIONS_GRAPHQL_URL)
.to_return(status: 200, body: { .to_return(status: 200, body: {
"data": { "data": {
......
...@@ -241,6 +241,15 @@ describe('SubscriptionManagementApp', () => { ...@@ -241,6 +241,15 @@ describe('SubscriptionManagementApp', () => {
pastSubscriptionsResolver, pastSubscriptionsResolver,
futureSubscriptionsResolver, futureSubscriptionsResolver,
]); ]);
jest
.spyOn(wrapper.vm.$apollo.queries.currentSubscription, 'refetch')
.mockImplementation(jest.fn());
jest
.spyOn(wrapper.vm.$apollo.queries.pastLicenseHistoryEntries, 'refetch')
.mockImplementation(jest.fn());
jest
.spyOn(wrapper.vm.$apollo.queries.futureLicenseHistoryEntries, 'refetch')
.mockImplementation(jest.fn());
await waitForPromises(); await waitForPromises();
}); });
...@@ -270,6 +279,20 @@ describe('SubscriptionManagementApp', () => { ...@@ -270,6 +279,20 @@ describe('SubscriptionManagementApp', () => {
subscriptionActivationFutureDatedNotificationTitle, subscriptionActivationFutureDatedNotificationTitle,
); );
}); });
it('calls refetch to update local state', async () => {
await findSubscriptionBreakdown().vm.$emit(
SUBSCRIPTION_ACTIVATION_SUCCESS_EVENT,
license.ULTIMATE_FUTURE_DATED,
);
expect(wrapper.vm.$apollo.queries.currentSubscription.refetch).toHaveBeenCalledTimes(1);
expect(wrapper.vm.$apollo.queries.pastLicenseHistoryEntries.refetch).toHaveBeenCalledTimes(
1,
);
expect(
wrapper.vm.$apollo.queries.futureLicenseHistoryEntries.refetch,
).toHaveBeenCalledTimes(1);
});
}); });
describe('with active license', () => { describe('with active license', () => {
......
...@@ -178,7 +178,6 @@ describe('SubscriptionActivationForm', () => { ...@@ -178,7 +178,6 @@ describe('SubscriptionActivationForm', () => {
const mutationMock = jest.fn().mockResolvedValue(activateLicenseMutationResponse.SUCCESS); const mutationMock = jest.fn().mockResolvedValue(activateLicenseMutationResponse.SUCCESS);
beforeEach(async () => { beforeEach(async () => {
createComponentWithApollo({ mutationMock, mountMethod: mount }); createComponentWithApollo({ mutationMock, mountMethod: mount });
jest.spyOn(wrapper.vm, 'updateSubscriptionAppCache').mockImplementation();
await findActivationCodeInput().vm.$emit('input', fakeActivationCode); await findActivationCodeInput().vm.$emit('input', fakeActivationCode);
await findAgreementCheckboxInput().trigger('click'); await findAgreementCheckboxInput().trigger('click');
findActivateSubscriptionForm().vm.$emit('submit', createFakeEvent()); findActivateSubscriptionForm().vm.$emit('submit', createFakeEvent());
...@@ -201,10 +200,6 @@ describe('SubscriptionActivationForm', () => { ...@@ -201,10 +200,6 @@ describe('SubscriptionActivationForm', () => {
[activateLicenseMutationResponse.SUCCESS.data.gitlabSubscriptionActivate.license], [activateLicenseMutationResponse.SUCCESS.data.gitlabSubscriptionActivate.license],
]); ]);
}); });
it('calls the method to update the cache', () => {
expect(wrapper.vm.updateSubscriptionAppCache).toHaveBeenCalledTimes(1);
});
}); });
describe('when the mutation is not successful', () => { describe('when the mutation is not successful', () => {
......
import { import { getErrorsAsData, getLicenseFromData } from 'ee/admin/subscriptions/show/graphql/utils';
getErrorsAsData,
getLicenseFromData,
updateSubscriptionAppCache,
} from 'ee/admin/subscriptions/show/graphql/utils';
import { activateLicenseMutationResponse } from '../mock_data';
describe('graphQl utils', () => { describe('graphQl utils', () => {
describe('getLicenseFromData', () => { describe('getLicenseFromData', () => {
...@@ -63,46 +58,4 @@ describe('graphQl utils', () => { ...@@ -63,46 +58,4 @@ describe('graphQl utils', () => {
expect(result).toEqual([]); expect(result).toEqual([]);
}); });
}); });
describe('updateSubscriptionAppCache', () => {
const cache = {
readQuery: jest.fn(() => ({ licenseHistoryEntries: { nodes: [] } })),
writeQuery: jest.fn(),
};
it('calls writeQuery the correct number of times', () => {
updateSubscriptionAppCache(cache, activateLicenseMutationResponse.SUCCESS);
expect(cache.writeQuery).toHaveBeenCalledTimes(2);
});
it('calls writeQuery the first time to update the current subscription', () => {
updateSubscriptionAppCache(cache, activateLicenseMutationResponse.SUCCESS);
expect(cache.writeQuery.mock.calls[0][0]).toEqual(
expect.objectContaining({
data: {
currentLicense:
activateLicenseMutationResponse.SUCCESS.data.gitlabSubscriptionActivate.license,
},
}),
);
});
it('calls writeQuery the second time to update the subscription history', () => {
updateSubscriptionAppCache(cache, activateLicenseMutationResponse.SUCCESS);
expect(cache.writeQuery.mock.calls[1][0]).toEqual(
expect.objectContaining({
data: {
licenseHistoryEntries: {
nodes: [
activateLicenseMutationResponse.SUCCESS.data.gitlabSubscriptionActivate.license,
],
},
},
}),
);
});
});
}); });
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