Commit 92613016 authored by Angelo Gulina's avatar Angelo Gulina Committed by David O'Regan

Add Sync Button logic and connect to endpoint

The button will show only with a Cloud License
The sync will show the appropriate message and instruction
in case of failure
parent 3e651e61
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { pick, some } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { import {
licensedToHeaderText, licensedToHeaderText,
manageSubscriptionButtonText, manageSubscriptionButtonText,
subscriptionDetailsHeaderText, subscriptionDetailsHeaderText,
subscriptionType,
syncSubscriptionButtonText, syncSubscriptionButtonText,
notificationType,
} from '../constants'; } from '../constants';
import SubscriptionDetailsCard from './subscription_details_card.vue'; import SubscriptionDetailsCard from './subscription_details_card.vue';
import SubscriptionDetailsHistory from './subscription_details_history.vue'; import SubscriptionDetailsHistory from './subscription_details_history.vue';
...@@ -22,11 +26,13 @@ export default { ...@@ -22,11 +26,13 @@ export default {
}, },
name: 'SubscriptionBreakdown', name: 'SubscriptionBreakdown',
components: { components: {
SubscriptionDetailsHistory,
GlButton, GlButton,
SubscriptionDetailsCard, SubscriptionDetailsCard,
SubscriptionDetailsHistory,
SubscriptionDetailsUserInfo, SubscriptionDetailsUserInfo,
SubscriptionSyncNotifications: () => import('./subscription_sync_notifications.vue'),
}, },
inject: ['subscriptionSyncPath'],
props: { props: {
subscription: { subscription: {
type: Object, type: Object,
...@@ -39,11 +45,16 @@ export default { ...@@ -39,11 +45,16 @@ export default {
}, },
data() { data() {
return { return {
subscriptionDetailsFields, hasAsyncActivity: false,
licensedToFields, licensedToFields,
notification: null,
subscriptionDetailsFields,
}; };
}, },
computed: { computed: {
canSyncSubscription() {
return this.subscriptionSyncPath && this.subscription.type === subscriptionType.CLOUD;
},
canMangeSubscription() { canMangeSubscription() {
return false; return false;
}, },
...@@ -53,15 +64,44 @@ export default { ...@@ -53,15 +64,44 @@ export default {
hasSubscriptionHistory() { hasSubscriptionHistory() {
return Boolean(this.subscriptionList.length); return Boolean(this.subscriptionList.length);
}, },
shouldShowFooter() {
return some(pick(this, ['canSyncSubscription', 'canMangeSubscription']), Boolean);
},
subscriptionHistory() { subscriptionHistory() {
return this.hasSubscriptionHistory ? this.subscriptionList : [this.subscription]; return this.hasSubscriptionHistory ? this.subscriptionList : [this.subscription];
}, },
}, },
methods: {
didDismissSuccessAlert() {
this.notification = null;
},
syncSubscription() {
this.hasAsyncActivity = true;
this.notification = null;
axios
.post(this.subscriptionSyncPath)
.then(() => {
this.notification = notificationType.SYNC_SUCCESS;
})
.catch(() => {
this.notification = notificationType.SYNC_FAILURE;
})
.finally(() => {
this.hasAsyncActivity = false;
});
},
},
}; };
</script> </script>
<template> <template>
<div> <div>
<subscription-sync-notifications
v-if="notification"
class="mb-4"
:notification="notification"
@success-alert-dismissed="didDismissSuccessAlert"
/>
<section class="row gl-mb-5"> <section class="row gl-mb-5">
<div class="col-md-6 gl-mb-5"> <div class="col-md-6 gl-mb-5">
<subscription-details-card <subscription-details-card
...@@ -69,11 +109,18 @@ export default { ...@@ -69,11 +109,18 @@ export default {
:header-text="$options.i18n.subscriptionDetailsHeaderText" :header-text="$options.i18n.subscriptionDetailsHeaderText"
:subscription="subscription" :subscription="subscription"
> >
<template v-if="canMangeSubscription" #footer> <template v-if="shouldShowFooter" #footer>
<gl-button category="primary" variant="confirm"> <gl-button
v-if="canSyncSubscription"
category="primary"
:loading="hasAsyncActivity"
variant="confirm"
data-testid="subscription-sync-action"
@click="syncSubscription"
>
{{ $options.i18n.syncSubscriptionButtonText }} {{ $options.i18n.syncSubscriptionButtonText }}
</gl-button> </gl-button>
<gl-button> <gl-button v-if="canMangeSubscription">
{{ $options.i18n.manageSubscriptionButtonText }} {{ $options.i18n.manageSubscriptionButtonText }}
</gl-button> </gl-button>
</template> </template>
......
...@@ -4,13 +4,9 @@ import { ...@@ -4,13 +4,9 @@ import {
manualSyncFailureText, manualSyncFailureText,
manualSyncFailureTitle, manualSyncFailureTitle,
manualSyncSuccessfulTitle, manualSyncSuccessfulTitle,
notificationType,
} from '../constants'; } from '../constants';
export const notificationType = {
SYNC_FAILURE: 'SYNC_FAILURE',
SYNC_SUCCESS: 'SYNC_SUCCESS',
};
export const SUCCESS_ALERT_DISMISSED_EVENT = 'success-alert-dismissed'; export const SUCCESS_ALERT_DISMISSED_EVENT = 'success-alert-dismissed';
const notificationTypeValidator = (value) => const notificationTypeValidator = (value) =>
......
...@@ -71,6 +71,17 @@ export const subscriptionActivationForm = { ...@@ -71,6 +71,17 @@ export const subscriptionActivationForm = {
activateLabel: s__('CloudLicense|Activate'), activateLabel: s__('CloudLicense|Activate'),
}; };
export const userNotifications = {
manualSyncSuccessfulTitle: s__('SuperSonics|The subscription details synced successfully.'),
manualSyncFailureText: s__(
'SuperSonics|You can no longer sync your subscription details with GitLab. Get help for the most common connectivity issues by %{connectivityHelpLinkStart}troubleshooting the activation code%{connectivityHelpLinkEnd}.',
),
manualSyncFailureTitle: s__('SuperSonics|There is a connectivity issue.'),
};
export const notificationType = {
SYNC_FAILURE: 'SYNC_FAILURE',
SYNC_SUCCESS: 'SYNC_SUCCESS',
};
export const subscriptionType = { export const subscriptionType = {
CLOUD: 'cloud', CLOUD: 'cloud',
LEGACY: 'legacy', LEGACY: 'legacy',
......
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { helpPagePath } from '~/helpers/help_page_helper';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils'; import { parseBoolean } from '~/lib/utils/common_utils';
import CloudLicenseShowApp from '../components/app.vue'; import CloudLicenseShowApp from '../components/app.vue';
...@@ -17,7 +18,10 @@ export default () => { ...@@ -17,7 +18,10 @@ export default () => {
return null; return null;
} }
const { hasActiveLicense, freeTrialPath, buySubscriptionPath } = el.dataset; const { hasActiveLicense, freeTrialPath, buySubscriptionPath, subscriptionSyncPath } = el.dataset;
const connectivityHelpURL = helpPagePath('/user/admin_area/license.html', {
anchor: 'activate-gitlab-ee-with-a-license',
});
return new Vue({ return new Vue({
el, el,
...@@ -25,6 +29,8 @@ export default () => { ...@@ -25,6 +29,8 @@ export default () => {
provide: { provide: {
freeTrialPath, freeTrialPath,
buySubscriptionPath, buySubscriptionPath,
connectivityHelpURL,
subscriptionSyncPath,
}, },
render: (h) => render: (h) =>
h(CloudLicenseShowApp, { h(CloudLicenseShowApp, {
......
...@@ -56,7 +56,8 @@ module LicenseHelper ...@@ -56,7 +56,8 @@ module LicenseHelper
{ {
has_active_license: (has_active_license? ? 'true' : 'false'), has_active_license: (has_active_license? ? 'true' : 'false'),
free_trial_path: new_trial_url, free_trial_path: new_trial_url,
buy_subscription_path: ::EE::SUBSCRIPTIONS_PLANS_URL buy_subscription_path: ::EE::SUBSCRIPTIONS_PLANS_URL,
subscription_sync_path: sync_seat_link_admin_license_path
} }
end end
......
import { GlCard } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import SubscriptionBreakdown, { import SubscriptionBreakdown, {
licensedToFields, licensedToFields,
subscriptionDetailsFields, subscriptionDetailsFields,
...@@ -6,23 +9,42 @@ import SubscriptionBreakdown, { ...@@ -6,23 +9,42 @@ import SubscriptionBreakdown, {
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 SubscriptionDetailsHistory from 'ee/pages/admin/cloud_licenses/components/subscription_details_history.vue'; import SubscriptionDetailsHistory from 'ee/pages/admin/cloud_licenses/components/subscription_details_history.vue';
import SubscriptionDetailsUserInfo from 'ee/pages/admin/cloud_licenses/components/subscription_details_user_info.vue'; import SubscriptionDetailsUserInfo from 'ee/pages/admin/cloud_licenses/components/subscription_details_user_info.vue';
import SubscriptionSyncNotifications, {
SUCCESS_ALERT_DISMISSED_EVENT,
} from 'ee/pages/admin/cloud_licenses/components/subscription_sync_notifications.vue';
import { import {
licensedToHeaderText, licensedToHeaderText,
notificationType,
subscriptionDetailsHeaderText, subscriptionDetailsHeaderText,
} from 'ee/pages/admin/cloud_licenses/constants'; } from 'ee/pages/admin/cloud_licenses/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import { license, subscriptionHistory } from '../mock_data'; import { license, subscriptionHistory } from '../mock_data';
describe('Subscription Breakdown', () => { describe('Subscription Breakdown', () => {
let axiosMock;
let wrapper; let wrapper;
const [, legacyLicense] = subscriptionHistory;
const connectivityHelpURL = 'connectivity/help/url';
const subscriptionSyncPath = '/sync/path/';
const findDetailsCards = () => wrapper.findAllComponents(SubscriptionDetailsCard); const findDetailsCards = () => wrapper.findAllComponents(SubscriptionDetailsCard);
const findDetailsCardFooter = () => wrapper.find('.gl-card-footer');
const findDetailsHistory = () => wrapper.findComponent(SubscriptionDetailsHistory); const findDetailsHistory = () => wrapper.findComponent(SubscriptionDetailsHistory);
const findDetailsUserInfo = () => wrapper.findComponent(SubscriptionDetailsUserInfo); const findDetailsUserInfo = () => wrapper.findComponent(SubscriptionDetailsUserInfo);
const findSubscriptionSyncAction = () => wrapper.findByTestId('subscription-sync-action');
const findSubscriptionSyncNotifications = () =>
wrapper.findComponent(SubscriptionSyncNotifications);
const createComponent = ({ props, stubs } = {}) => { const createComponent = ({ props, stubs } = {}) => {
wrapper = extendedWrapper( wrapper = extendedWrapper(
shallowMount(SubscriptionBreakdown, { shallowMount(SubscriptionBreakdown, {
provide: {
connectivityHelpURL,
subscriptionSyncPath,
},
propsData: { propsData: {
subscription: license.ULTIMATE, subscription: license.ULTIMATE,
subscriptionList: subscriptionHistory, subscriptionList: subscriptionHistory,
...@@ -33,7 +55,12 @@ describe('Subscription Breakdown', () => { ...@@ -33,7 +55,12 @@ describe('Subscription Breakdown', () => {
); );
}; };
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
});
afterEach(() => { afterEach(() => {
axiosMock.restore();
wrapper.destroy(); wrapper.destroy();
}); });
...@@ -71,9 +98,87 @@ describe('Subscription Breakdown', () => { ...@@ -71,9 +98,87 @@ describe('Subscription Breakdown', () => {
expect(findDetailsUserInfo().props('subscription')).toBe(license.ULTIMATE); expect(findDetailsUserInfo().props('subscription')).toBe(license.ULTIMATE);
}); });
it.todo('shows a button to sync the subscription'); it('does not show notifications', () => {
expect(findSubscriptionSyncNotifications().exists()).toBe(false);
});
it('shows the subscription details footer', () => {
createComponent({ stubs: { GlCard, SubscriptionDetailsCard } });
expect(findDetailsCardFooter().exists()).toBe(true);
});
it('shows a button to sync the subscription', () => {
createComponent({ stubs: { GlCard, SubscriptionDetailsCard } });
expect(findSubscriptionSyncAction().exists()).toBe(true);
});
it.todo('shows a button to manage the subscription'); it.todo('shows a button to manage the subscription');
describe('with a legacy license', () => {
beforeEach(() => {
createComponent({
props: { subscription: legacyLicense },
stubs: { GlCard, SubscriptionDetailsCard },
});
});
it('does not show a button to sync the subscription', () => {
expect(findSubscriptionSyncAction().exists()).toBe(false);
});
it('does not show the subscription details footer', () => {
expect(findDetailsCardFooter().exists()).toBe(false);
});
it('does not show the sync subscription notifications', () => {
expect(findSubscriptionSyncNotifications().exists()).toBe(false);
});
});
describe('sync a subscription success', () => {
beforeEach(() => {
axiosMock.onPost(subscriptionSyncPath).reply(200, { success: true });
createComponent({ stubs: { GlCard, SubscriptionDetailsCard } });
findSubscriptionSyncAction().vm.$emit('click');
return waitForPromises();
});
it('shows a success notification', () => {
expect(findSubscriptionSyncNotifications().props('notification')).toBe(
notificationType.SYNC_SUCCESS,
);
});
it('dismisses the success notification', async () => {
findSubscriptionSyncNotifications().vm.$emit(SUCCESS_ALERT_DISMISSED_EVENT);
await nextTick();
expect(findSubscriptionSyncNotifications().exists()).toBe(false);
});
});
describe('sync a subscription failure', () => {
beforeEach(() => {
axiosMock.onPost(subscriptionSyncPath).reply(422, { success: false });
createComponent({ stubs: { GlCard, SubscriptionDetailsCard } });
findSubscriptionSyncAction().vm.$emit('click');
return waitForPromises();
});
it('shows a failure notification', () => {
expect(findSubscriptionSyncNotifications().props('notification')).toBe(
notificationType.SYNC_FAILURE,
);
});
it('dismisses the failure notification when retrying to sync', async () => {
await findSubscriptionSyncAction().vm.$emit('click');
expect(findSubscriptionSyncNotifications().exists()).toBe(false);
});
});
}); });
describe('with subscription history data', () => { describe('with subscription history data', () => {
...@@ -98,7 +203,7 @@ describe('Subscription Breakdown', () => { ...@@ -98,7 +203,7 @@ describe('Subscription Breakdown', () => {
expect(findDetailsUserInfo().exists()).toBe(false); expect(findDetailsUserInfo().exists()).toBe(false);
}); });
it('shows the current subscription as the only history item', () => { it('does not show details', () => {
createComponent({ props: { subscription: {}, subscriptionList: [] } }); createComponent({ props: { subscription: {}, subscriptionList: [] } });
expect(findDetailsUserInfo().exists()).toBe(false); expect(findDetailsUserInfo().exists()).toBe(false);
......
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import SubscriptionSyncNotifications, { import SubscriptionSyncNotifications, {
notificationType,
SUCCESS_ALERT_DISMISSED_EVENT, SUCCESS_ALERT_DISMISSED_EVENT,
} from 'ee/pages/admin/cloud_licenses/components/subscription_sync_notifications.vue'; } from 'ee/pages/admin/cloud_licenses/components/subscription_sync_notifications.vue';
import { import {
manualSyncFailureTitle, manualSyncFailureTitle,
manualSyncSuccessfulTitle, manualSyncSuccessfulTitle,
notificationType,
} from 'ee/pages/admin/cloud_licenses/constants'; } from 'ee/pages/admin/cloud_licenses/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
......
...@@ -94,7 +94,8 @@ RSpec.describe LicenseHelper do ...@@ -94,7 +94,8 @@ RSpec.describe LicenseHelper do
expect(helper.cloud_license_view_data).to eq({ has_active_license: 'true', expect(helper.cloud_license_view_data).to eq({ has_active_license: 'true',
free_trial_path: 'new_trial_url', free_trial_path: 'new_trial_url',
buy_subscription_path: 'subscriptions_plans_url' }) buy_subscription_path: 'subscriptions_plans_url',
subscription_sync_path: sync_seat_link_admin_license_path })
end end
end end
...@@ -104,7 +105,8 @@ RSpec.describe LicenseHelper do ...@@ -104,7 +105,8 @@ RSpec.describe LicenseHelper do
expect(helper.cloud_license_view_data).to eq({ has_active_license: 'false', expect(helper.cloud_license_view_data).to eq({ has_active_license: 'false',
free_trial_path: 'new_trial_url', free_trial_path: 'new_trial_url',
buy_subscription_path: 'subscriptions_plans_url' }) buy_subscription_path: 'subscriptions_plans_url',
subscription_sync_path: sync_seat_link_admin_license_path })
end end
end end
end end
......
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