Commit 373d112c authored by David O'Regan's avatar David O'Regan

Merge branch 'ag-325156-manual-sync-button' into 'master'

Manual Sync: Button and connection with backend

See merge request gitlab-org/gitlab!59612
parents 79dbf3c5 92613016
<script>
import { GlButton } from '@gitlab/ui';
import { pick, some } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import {
licensedToHeaderText,
manageSubscriptionButtonText,
subscriptionDetailsHeaderText,
subscriptionType,
syncSubscriptionButtonText,
notificationType,
} from '../constants';
import SubscriptionDetailsCard from './subscription_details_card.vue';
import SubscriptionDetailsHistory from './subscription_details_history.vue';
......@@ -22,11 +26,13 @@ export default {
},
name: 'SubscriptionBreakdown',
components: {
SubscriptionDetailsHistory,
GlButton,
SubscriptionDetailsCard,
SubscriptionDetailsHistory,
SubscriptionDetailsUserInfo,
SubscriptionSyncNotifications: () => import('./subscription_sync_notifications.vue'),
},
inject: ['subscriptionSyncPath'],
props: {
subscription: {
type: Object,
......@@ -39,11 +45,16 @@ export default {
},
data() {
return {
subscriptionDetailsFields,
hasAsyncActivity: false,
licensedToFields,
notification: null,
subscriptionDetailsFields,
};
},
computed: {
canSyncSubscription() {
return this.subscriptionSyncPath && this.subscription.type === subscriptionType.CLOUD;
},
canMangeSubscription() {
return false;
},
......@@ -53,15 +64,44 @@ export default {
hasSubscriptionHistory() {
return Boolean(this.subscriptionList.length);
},
shouldShowFooter() {
return some(pick(this, ['canSyncSubscription', 'canMangeSubscription']), Boolean);
},
subscriptionHistory() {
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>
<template>
<div>
<subscription-sync-notifications
v-if="notification"
class="mb-4"
:notification="notification"
@success-alert-dismissed="didDismissSuccessAlert"
/>
<section class="row gl-mb-5">
<div class="col-md-6 gl-mb-5">
<subscription-details-card
......@@ -69,11 +109,18 @@ export default {
:header-text="$options.i18n.subscriptionDetailsHeaderText"
:subscription="subscription"
>
<template v-if="canMangeSubscription" #footer>
<gl-button category="primary" variant="confirm">
<template v-if="shouldShowFooter" #footer>
<gl-button
v-if="canSyncSubscription"
category="primary"
:loading="hasAsyncActivity"
variant="confirm"
data-testid="subscription-sync-action"
@click="syncSubscription"
>
{{ $options.i18n.syncSubscriptionButtonText }}
</gl-button>
<gl-button>
<gl-button v-if="canMangeSubscription">
{{ $options.i18n.manageSubscriptionButtonText }}
</gl-button>
</template>
......
......@@ -4,13 +4,9 @@ import {
manualSyncFailureText,
manualSyncFailureTitle,
manualSyncSuccessfulTitle,
notificationType,
} from '../constants';
export const notificationType = {
SYNC_FAILURE: 'SYNC_FAILURE',
SYNC_SUCCESS: 'SYNC_SUCCESS',
};
export const SUCCESS_ALERT_DISMISSED_EVENT = 'success-alert-dismissed';
const notificationTypeValidator = (value) =>
......
......@@ -71,6 +71,17 @@ export const subscriptionActivationForm = {
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 = {
CLOUD: 'cloud',
LEGACY: 'legacy',
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { helpPagePath } from '~/helpers/help_page_helper';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import CloudLicenseShowApp from '../components/app.vue';
......@@ -17,7 +18,10 @@ export default () => {
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({
el,
......@@ -25,6 +29,8 @@ export default () => {
provide: {
freeTrialPath,
buySubscriptionPath,
connectivityHelpURL,
subscriptionSyncPath,
},
render: (h) =>
h(CloudLicenseShowApp, {
......
......@@ -56,7 +56,8 @@ module LicenseHelper
{
has_active_license: (has_active_license? ? 'true' : 'false'),
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
......
import { GlCard } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import SubscriptionBreakdown, {
licensedToFields,
subscriptionDetailsFields,
......@@ -6,23 +9,42 @@ import SubscriptionBreakdown, {
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 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 {
licensedToHeaderText,
notificationType,
subscriptionDetailsHeaderText,
} from 'ee/pages/admin/cloud_licenses/constants';
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';
describe('Subscription Breakdown', () => {
let axiosMock;
let wrapper;
const [, legacyLicense] = subscriptionHistory;
const connectivityHelpURL = 'connectivity/help/url';
const subscriptionSyncPath = '/sync/path/';
const findDetailsCards = () => wrapper.findAllComponents(SubscriptionDetailsCard);
const findDetailsCardFooter = () => wrapper.find('.gl-card-footer');
const findDetailsHistory = () => wrapper.findComponent(SubscriptionDetailsHistory);
const findDetailsUserInfo = () => wrapper.findComponent(SubscriptionDetailsUserInfo);
const findSubscriptionSyncAction = () => wrapper.findByTestId('subscription-sync-action');
const findSubscriptionSyncNotifications = () =>
wrapper.findComponent(SubscriptionSyncNotifications);
const createComponent = ({ props, stubs } = {}) => {
wrapper = extendedWrapper(
shallowMount(SubscriptionBreakdown, {
provide: {
connectivityHelpURL,
subscriptionSyncPath,
},
propsData: {
subscription: license.ULTIMATE,
subscriptionList: subscriptionHistory,
......@@ -33,7 +55,12 @@ describe('Subscription Breakdown', () => {
);
};
beforeEach(() => {
axiosMock = new AxiosMockAdapter(axios);
});
afterEach(() => {
axiosMock.restore();
wrapper.destroy();
});
......@@ -71,9 +98,87 @@ describe('Subscription Breakdown', () => {
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');
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', () => {
......@@ -98,7 +203,7 @@ describe('Subscription Breakdown', () => {
expect(findDetailsUserInfo().exists()).toBe(false);
});
it('shows the current subscription as the only history item', () => {
it('does not show details', () => {
createComponent({ props: { subscription: {}, subscriptionList: [] } });
expect(findDetailsUserInfo().exists()).toBe(false);
......
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SubscriptionSyncNotifications, {
notificationType,
SUCCESS_ALERT_DISMISSED_EVENT,
} from 'ee/pages/admin/cloud_licenses/components/subscription_sync_notifications.vue';
import {
manualSyncFailureTitle,
manualSyncSuccessfulTitle,
notificationType,
} from 'ee/pages/admin/cloud_licenses/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
......
......@@ -94,7 +94,8 @@ RSpec.describe LicenseHelper do
expect(helper.cloud_license_view_data).to eq({ has_active_license: 'true',
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
......@@ -104,7 +105,8 @@ RSpec.describe LicenseHelper do
expect(helper.cloud_license_view_data).to eq({ has_active_license: 'false',
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
......
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