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