Commit 0e9ea31e authored by Vitaly Slobodin's avatar Vitaly Slobodin

Merge branch 'ag/track-zuora-cc-events-all' into 'master'

Track Zuora events from all components

See merge request gitlab-org/gitlab!84065
parents 57c6d334 b5b4bf98
...@@ -8,7 +8,7 @@ export default { ...@@ -8,7 +8,7 @@ export default {
components: { components: {
GlLoadingIcon, GlLoadingIcon,
}, },
mixins: [Tracking.mixin()], mixins: [Tracking.mixin({ category: 'Zuora_cc' })],
props: { props: {
active: { active: {
type: Boolean, type: Boolean,
...@@ -60,11 +60,18 @@ export default { ...@@ -60,11 +60,18 @@ export default {
this.$emit('success'); this.$emit('success');
} else { } else {
this.$emit('error', response?.errorMessage); this.$emit('error', response?.errorMessage);
this.track('error', {
label: 'payment_form_submitted',
property: response?.errorMessage,
});
} }
}, },
renderZuoraIframe() { renderZuoraIframe() {
const params = { ...this.paymentFormParams, ...ZUORA_IFRAME_OVERRIDE_PARAMS }; const params = { ...this.paymentFormParams, ...ZUORA_IFRAME_OVERRIDE_PARAMS };
window.Z.runAfterRender(this.zuoraIframeRendered); window.Z.runAfterRender(() => {
this.zuoraIframeRendered();
this.track('iframe_loaded');
});
window.Z.render(params, {}, this.handleZuoraCallback); window.Z.render(params, {}, this.handleZuoraCallback);
}, },
}, },
......
...@@ -13,11 +13,13 @@ import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants'; ...@@ -13,11 +13,13 @@ import { GENERAL_ERROR_MESSAGE } from 'ee/vue_shared/purchase_flow/constants';
import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql'; import activateNextStepMutation from 'ee/vue_shared/purchase_flow/graphql/mutations/activate_next_step.mutation.graphql';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import Tracking from '~/tracking';
export default { export default {
components: { components: {
GlLoadingIcon, GlLoadingIcon,
}, },
mixins: [Tracking.mixin({ category: 'Zuora_cc' })],
props: { props: {
active: { active: {
type: Boolean, type: Boolean,
...@@ -57,6 +59,7 @@ export default { ...@@ -57,6 +59,7 @@ export default {
zuoraIframeRendered() { zuoraIframeRendered() {
this.isLoading = false; this.isLoading = false;
this.zuoraLoaded = true; this.zuoraLoaded = true;
this.track('iframe_loaded');
}, },
fetchPaymentFormParams() { fetchPaymentFormParams() {
this.isLoading = true; this.isLoading = true;
...@@ -66,8 +69,12 @@ export default { ...@@ -66,8 +69,12 @@ export default {
this.paymentFormParams = data; this.paymentFormParams = data;
this.renderZuoraIframe(); this.renderZuoraIframe();
}) })
.catch(() => { .catch((error) => {
createFlash({ message: ERROR_LOADING_PAYMENT_FORM }); createFlash({ message: ERROR_LOADING_PAYMENT_FORM });
this.track('error', {
label: 'payment_form_fetch_params',
property: error?.message,
});
}); });
}, },
loadZuoraScript() { loadZuoraScript() {
...@@ -82,7 +89,7 @@ export default { ...@@ -82,7 +89,7 @@ export default {
document.head.appendChild(this.zuoraScriptEl); document.head.appendChild(this.zuoraScriptEl);
} }
}, },
paymentFormSubmitted({ refId }) { paymentFormSubmitted({ refId } = {}) {
this.isLoading = true; this.isLoading = true;
return Api.fetchPaymentMethodDetails(refId) return Api.fetchPaymentMethodDetails(refId)
...@@ -98,10 +105,15 @@ export default { ...@@ -98,10 +105,15 @@ export default {
}) })
.then((paymentMethod) => convertObjectPropsToCamelCase(paymentMethod)) .then((paymentMethod) => convertObjectPropsToCamelCase(paymentMethod))
.then((paymentMethod) => this.updateState({ paymentMethod })) .then((paymentMethod) => this.updateState({ paymentMethod }))
.then(() => this.track('success'))
.then(() => this.activateNextStep()) .then(() => this.activateNextStep())
.catch((error) => .catch((error) => {
createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true }), createFlash({ message: GENERAL_ERROR_MESSAGE, error, captureError: true });
) this.track('error', {
label: 'payment_form_submitted',
property: error?.message,
});
})
.finally(() => { .finally(() => {
this.isLoading = false; this.isLoading = false;
}); });
......
...@@ -5,12 +5,14 @@ import Vuex from 'vuex'; ...@@ -5,12 +5,14 @@ import Vuex from 'vuex';
import Component from 'ee/subscriptions/new/components/checkout/zuora.vue'; import Component from 'ee/subscriptions/new/components/checkout/zuora.vue';
import { getStoreConfig } from 'ee/subscriptions/new/store'; import { getStoreConfig } from 'ee/subscriptions/new/store';
import * as types from 'ee/subscriptions/new/store/mutation_types'; import * as types from 'ee/subscriptions/new/store/mutation_types';
import { mockTracking } from 'helpers/tracking_helper';
describe('Zuora', () => { describe('Zuora', () => {
Vue.use(Vuex); Vue.use(Vuex);
let store; let store;
let wrapper; let wrapper;
let trackingSpy;
const actionMocks = { const actionMocks = {
startLoadingZuoraScript: jest.fn(), startLoadingZuoraScript: jest.fn(),
...@@ -36,6 +38,8 @@ describe('Zuora', () => { ...@@ -36,6 +38,8 @@ describe('Zuora', () => {
}, },
store, store,
}); });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}; };
const findLoading = () => wrapper.findComponent(GlLoadingIcon); const findLoading = () => wrapper.findComponent(GlLoadingIcon);
...@@ -107,18 +111,22 @@ describe('Zuora', () => { ...@@ -107,18 +111,22 @@ describe('Zuora', () => {
}); });
}); });
describe('renderZuoraIframe', () => { describe('when rendering', () => {
it('is called when the paymentFormParams are updated', () => { beforeEach(() => {
createComponent(); createComponent();
expect(actionMocks.zuoraIframeRendered).not.toHaveBeenCalled();
store.commit(types.UPDATE_PAYMENT_FORM_PARAMS, {}); store.commit(types.UPDATE_PAYMENT_FORM_PARAMS, {});
return nextTick();
});
it('renderZuoraIframe is called when the paymentFormParams are updated', () => {
expect(actionMocks.zuoraIframeRendered).toHaveBeenCalled();
wrapper.vm.handleZuoraCallback();
expect(actionMocks.paymentFormSubmitted).toHaveBeenCalled();
});
return nextTick().then(() => { it('tracks frame_loaded event', () => {
expect(actionMocks.zuoraIframeRendered).toHaveBeenCalled(); expect(trackingSpy).toHaveBeenCalledWith('Zuora_cc', 'iframe_loaded', {
wrapper.vm.handleZuoraCallback(); category: 'Zuora_cc',
expect(actionMocks.paymentFormSubmitted).toHaveBeenCalled();
}); });
}); });
}); });
...@@ -130,12 +138,25 @@ describe('Zuora', () => { ...@@ -130,12 +138,25 @@ describe('Zuora', () => {
expect(wrapper.emitted().success.length).toEqual(1); expect(wrapper.emitted().success.length).toEqual(1);
}); });
it('emits error with message', async () => { describe('with an error response', () => {
createComponent(); beforeEach(() => {
wrapper.vm.handleZuoraCallback({ errorMessage: '1337' }); createComponent();
await nextTick(); wrapper.vm.handleZuoraCallback({ errorMessage: '1337' });
expect(wrapper.emitted().error.length).toEqual(1); return nextTick();
expect(wrapper.emitted().error[0]).toEqual(['1337']); });
it('emits error with message', async () => {
expect(wrapper.emitted().error.length).toEqual(1);
expect(wrapper.emitted().error[0]).toEqual(['1337']);
});
it('tracks Zuora error', () => {
expect(trackingSpy).toHaveBeenCalledWith('Zuora_cc', 'error', {
label: 'payment_form_submitted',
property: '1337',
category: 'Zuora_cc',
});
});
}); });
}); });
}); });
...@@ -12,12 +12,14 @@ import { stateData as initialStateData } from 'ee_jest/subscriptions/mock_data'; ...@@ -12,12 +12,14 @@ import { stateData as initialStateData } from 'ee_jest/subscriptions/mock_data';
import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper'; import { createMockApolloProvider } from 'ee_jest/vue_shared/purchase_flow/spec_helper';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import flushPromises from 'helpers/flush_promises'; import flushPromises from 'helpers/flush_promises';
import { mockTracking } from 'helpers/tracking_helper';
Vue.use(VueApollo); Vue.use(VueApollo);
describe('Zuora', () => { describe('Zuora', () => {
let axiosMock; let axiosMock;
let wrapper; let wrapper;
let trackingSpy;
const createComponent = (props = {}, data = {}, apolloLocalState = {}) => { const createComponent = (props = {}, data = {}, apolloLocalState = {}) => {
const apolloProvider = createMockApolloProvider(STEPS, STEPS[1], gitLabResolvers); const apolloProvider = createMockApolloProvider(STEPS, STEPS[1], gitLabResolvers);
...@@ -26,7 +28,8 @@ describe('Zuora', () => { ...@@ -26,7 +28,8 @@ describe('Zuora', () => {
data: merge({}, initialStateData, apolloLocalState), data: merge({}, initialStateData, apolloLocalState),
}); });
return shallowMount(Zuora, { wrapper = shallowMount(Zuora, {
apolloProvider,
propsData: { propsData: {
active: true, active: true,
...props, ...props,
...@@ -35,6 +38,8 @@ describe('Zuora', () => { ...@@ -35,6 +38,8 @@ describe('Zuora', () => {
return { ...data }; return { ...data };
}, },
}); });
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
}; };
const findLoading = () => wrapper.findComponent(GlLoadingIcon); const findLoading = () => wrapper.findComponent(GlLoadingIcon);
...@@ -50,6 +55,7 @@ describe('Zuora', () => { ...@@ -50,6 +55,7 @@ describe('Zuora', () => {
axiosMock = new AxiosMockAdapter(axios); axiosMock = new AxiosMockAdapter(axios);
axiosMock.onGet(`/-/subscriptions/payment_form`).reply(200, {}); axiosMock.onGet(`/-/subscriptions/payment_form`).reply(200, {});
axiosMock.onGet(`/-/subscriptions/payment_method`).reply(200, {});
}); });
afterEach(() => { afterEach(() => {
...@@ -59,7 +65,7 @@ describe('Zuora', () => { ...@@ -59,7 +65,7 @@ describe('Zuora', () => {
describe('when active', () => { describe('when active', () => {
beforeEach(async () => { beforeEach(async () => {
wrapper = createComponent({}, { isLoading: false }); createComponent({}, { isLoading: false });
}); });
it('shows the loading icon', () => { it('shows the loading icon', () => {
...@@ -72,7 +78,7 @@ describe('Zuora', () => { ...@@ -72,7 +78,7 @@ describe('Zuora', () => {
describe('when toggling the loading indicator', () => { describe('when toggling the loading indicator', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({}, { isLoading: true }); createComponent({}, { isLoading: true });
wrapper.vm.zuoraScriptEl.onload(); wrapper.vm.zuoraScriptEl.onload();
}); });
...@@ -88,7 +94,7 @@ describe('Zuora', () => { ...@@ -88,7 +94,7 @@ describe('Zuora', () => {
describe('when not active', () => { describe('when not active', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ active: false }); createComponent({ active: false });
}); });
it('the zuora_payment selector should not be visible', () => { it('the zuora_payment selector should not be visible', () => {
...@@ -96,9 +102,90 @@ describe('Zuora', () => { ...@@ -96,9 +102,90 @@ describe('Zuora', () => {
}); });
}); });
describe('when fetch payment params is successful', () => {
beforeEach(() => {
createComponent();
wrapper.vm.zuoraScriptEl.onload();
return flushPromises();
});
it('tracks frame_loaded event', () => {
expect(trackingSpy).toHaveBeenCalledWith('Zuora_cc', 'iframe_loaded', {
category: 'Zuora_cc',
});
});
});
describe('when fetch payment params is not successful', () => {
beforeEach(() => {
createComponent({}, { isLoading: false });
wrapper.vm.zuoraScriptEl.onload();
axiosMock.onGet(`/-/subscriptions/payment_form`).reply(401, {});
return flushPromises();
});
it('tracks the error event', () => {
expect(trackingSpy).toHaveBeenCalledTimes(1);
expect(trackingSpy).toHaveBeenCalledWith('Zuora_cc', 'error', {
label: 'payment_form_fetch_params',
property: 'Request failed with status code 401',
category: 'Zuora_cc',
});
});
});
describe('when fetch payment details is successful', () => {
beforeEach(() => {
window.Z = {
runAfterRender(fn) {
return Promise.resolve().then(fn);
},
render(params, object, fn) {
return Promise.resolve().then(fn);
},
};
createComponent({}, { isLoading: false });
wrapper.vm.zuoraScriptEl.onload();
return flushPromises();
});
it('tracks success event', () => {
expect(trackingSpy).toHaveBeenCalledTimes(2);
expect(trackingSpy).toHaveBeenCalledWith('Zuora_cc', 'success', { category: 'Zuora_cc' });
});
});
describe('when fetch payment details is not successful', () => {
beforeEach(() => {
window.Z = {
runAfterRender(fn) {
return Promise.resolve().then(fn);
},
render(params, object, fn) {
return Promise.resolve().then(fn);
},
};
createComponent({}, { isLoading: false });
wrapper.vm.zuoraScriptEl.onload();
axiosMock.onGet(`/-/subscriptions/payment_method`).reply(401, {});
return flushPromises();
});
it('tracks the error event', () => {
expect(trackingSpy).toHaveBeenCalledTimes(2);
expect(trackingSpy).toHaveBeenCalledWith('Zuora_cc', 'error', {
label: 'payment_form_submitted',
property: 'Request failed with status code 401',
category: 'Zuora_cc',
});
});
});
describe.each(['', '111111'])('when rendering the iframe with account id: %s', (id) => { describe.each(['', '111111'])('when rendering the iframe with account id: %s', (id) => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ accountId: id }, { isLoading: false }); createComponent({ accountId: id }, { isLoading: false });
wrapper.vm.zuoraScriptEl.onload(); wrapper.vm.zuoraScriptEl.onload();
return flushPromises(); return flushPromises();
}); });
......
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