From 2e954c531afc4ae76c8b0e4f9e1356c02c8e4302 Mon Sep 17 00:00:00 2001 From: Yu Long Date: Mon, 18 Nov 2024 17:49:00 +0100 Subject: [PATCH] refactor(analytics): send redirect error event --- .../lib/src/components/Redirect/Redirect.tsx | 19 ++++++- .../internal/UIElement/UIElement.tsx | 20 +++++--- .../lib/src/core/Analytics/EventsQueue.ts | 10 ++-- .../core/Analytics/analyticsPreProcessor.ts | 3 +- packages/lib/src/core/Analytics/constants.ts | 4 ++ packages/lib/src/core/Analytics/types.ts | 4 +- packages/lib/src/core/Analytics/utils.ts | 51 ++++++++++--------- 7 files changed, 71 insertions(+), 40 deletions(-) diff --git a/packages/lib/src/components/Redirect/Redirect.tsx b/packages/lib/src/components/Redirect/Redirect.tsx index 8abe8425b6..33f8f0955f 100644 --- a/packages/lib/src/components/Redirect/Redirect.tsx +++ b/packages/lib/src/components/Redirect/Redirect.tsx @@ -6,6 +6,7 @@ import RedirectButton from '../internal/RedirectButton'; import { TxVariants } from '../tx-variants'; import { RedirectConfiguration } from './types'; import collectBrowserInfo from '../../utils/browserInfo'; +import { ANALYTICS_ERROR_CODE, ANALYTICS_ERROR_TYPE, ANALYTICS_EVENT } from '../../core/Analytics/constants'; class RedirectElement extends UIElement { public static type = TxVariants.redirect; @@ -23,6 +24,15 @@ class RedirectElement extends UIElement { }; } + private handleRedirectError = () => { + super.submitAnalytics({ + component: this.props.paymentMethodType, + type: ANALYTICS_EVENT.error, + errorType: ANALYTICS_ERROR_TYPE.redirect, + code: ANALYTICS_ERROR_CODE.redirect + }); + }; + get isValid() { return true; } @@ -33,7 +43,14 @@ class RedirectElement extends UIElement { render() { if (this.props.url && this.props.method) { - return ; + return ( + + ); } if (this.props.showPayButton) { diff --git a/packages/lib/src/components/internal/UIElement/UIElement.tsx b/packages/lib/src/components/internal/UIElement/UIElement.tsx index 7fbdeee913..945de0637b 100644 --- a/packages/lib/src/components/internal/UIElement/UIElement.tsx +++ b/packages/lib/src/components/internal/UIElement/UIElement.tsx @@ -161,17 +161,25 @@ export abstract class UIElement

exten * - otherwise, distinguish cards from non-cards: cards will use their static type property, everything else will use props.type */ try { - let component = this.constructor['analyticsType']; - if (!component) { - component = this.constructor['type'] === 'scheme' || this.constructor['type'] === 'bcmc' ? this.constructor['type'] : this.props.type; - } - - this.props.modules.analytics.sendAnalytics(component, analyticsObj, uiElementProps); + this.props.modules.analytics.sendAnalytics(this.getComponent(analyticsObj), analyticsObj, uiElementProps); } catch (error) { console.warn('Failed to submit the analytics event. Error:', error); } } + private getComponent({ component }: SendAnalyticsObject): string { + if (component) { + return component; + } + if (this.constructor['analyticsType']) { + return this.constructor['analyticsType']; + } + if (this.constructor['type'] === 'scheme' || this.constructor['type'] === 'bcmc') { + return this.constructor['type']; + } + return this.props.type; + } + public submit(): void { if (!this.isValid) { this.showValidation(); diff --git a/packages/lib/src/core/Analytics/EventsQueue.ts b/packages/lib/src/core/Analytics/EventsQueue.ts index 71deae0e9e..f48fadb8b4 100644 --- a/packages/lib/src/core/Analytics/EventsQueue.ts +++ b/packages/lib/src/core/Analytics/EventsQueue.ts @@ -24,7 +24,7 @@ const EventsQueue = ({ analyticsContext, clientKey, analyticsPath }: EventQueueP logs: [] }; - const runQueue = (checkoutAttemptId: string): Promise => { + const runQueue = async (checkoutAttemptId: string): Promise => { if (!caActions.info.length && !caActions.logs.length && !caActions.errors.length) { return Promise.resolve(null); } @@ -35,7 +35,7 @@ const EventsQueue = ({ analyticsContext, clientKey, analyticsPath }: EventQueueP path: `${analyticsPath}/${checkoutAttemptId}?clientKey=${clientKey}` }; - const promise = httpPost(options, caActions) + return httpPost(options, caActions) .then(() => { // Succeed, silently return undefined; @@ -44,11 +44,9 @@ const EventsQueue = ({ analyticsContext, clientKey, analyticsPath }: EventQueueP // Caught, silently, at http level. We do not expect this catch block to ever fire, but... just in case... console.debug('### EventsQueue:::: send has failed'); }); - - return promise; }; - const eqModule: EventsQueueModule = { + return { add: (type, actionObj) => { caActions[type].push(actionObj); }, @@ -66,8 +64,6 @@ const EventsQueue = ({ analyticsContext, clientKey, analyticsPath }: EventQueueP // Expose getter for testing purposes getQueue: () => caActions }; - - return eqModule; }; export default EventsQueue; diff --git a/packages/lib/src/core/Analytics/analyticsPreProcessor.ts b/packages/lib/src/core/Analytics/analyticsPreProcessor.ts index 9707a333e7..7030da0a74 100644 --- a/packages/lib/src/core/Analytics/analyticsPreProcessor.ts +++ b/packages/lib/src/core/Analytics/analyticsPreProcessor.ts @@ -145,7 +145,8 @@ export const analyticsPreProcessor = (analyticsModule: AnalyticsModule) => { /** * ERRORS */ - case THREEDS2_ERROR: { + case THREEDS2_ERROR: + case ANALYTICS_EVENT.error: { const { message, code, errorType } = analyticsObj; analyticsModule.createAnalyticsEvent({ event: ANALYTICS_EVENT.error, diff --git a/packages/lib/src/core/Analytics/constants.ts b/packages/lib/src/core/Analytics/constants.ts index b805fe28d7..a9a0da853f 100644 --- a/packages/lib/src/core/Analytics/constants.ts +++ b/packages/lib/src/core/Analytics/constants.ts @@ -30,6 +30,10 @@ export const ANALYTICS_ERROR_TYPE = { threeDS2: 'ThreeDS2' }; +export const ANALYTICS_ERROR_CODE = { + redirect: '600' +}; + export const ANALYTICS_ACTION_STR = 'action'; export const ANALYTICS_SUBMIT_STR = 'submit'; export const ANALYTICS_SELECTED_STR = 'selected'; diff --git a/packages/lib/src/core/Analytics/types.ts b/packages/lib/src/core/Analytics/types.ts index c318174232..8a0599d891 100644 --- a/packages/lib/src/core/Analytics/types.ts +++ b/packages/lib/src/core/Analytics/types.ts @@ -115,7 +115,9 @@ export type CreateAnalyticsEventObject = { export type EventQueueProps = Pick & { analyticsPath: string }; -export type SendAnalyticsObject = Omit; +export interface SendAnalyticsObject extends Omit { + component?: string; +} export type FieldErrorAnalyticsObject = { fieldType: string; diff --git a/packages/lib/src/core/Analytics/utils.ts b/packages/lib/src/core/Analytics/utils.ts index ee6ac4a96f..89bd77f985 100644 --- a/packages/lib/src/core/Analytics/utils.ts +++ b/packages/lib/src/core/Analytics/utils.ts @@ -30,30 +30,33 @@ export const getUTCTimestamp = () => Date.now(); * * All objects can also have a "metadata" object of key-value pairs */ -export const createAnalyticsObject = (aObj: CreateAnalyticsObject): AnalyticsObject => ({ - timestamp: String(getUTCTimestamp()), - component: aObj.component, - id: uuid(), - /** ERROR */ - ...(aObj.event === 'error' && { code: aObj.code, errorType: aObj.errorType, message: aObj.message }), // error event - /** LOG */ - ...(aObj.event === 'log' && { type: aObj.type, message: aObj.message }), // log event - ...(aObj.event === 'log' && (aObj.type === ANALYTICS_ACTION_STR || aObj.type === THREEDS2_FULL) && { subType: aObj.subtype }), // only added if we have a log event of Action type or ThreeDS2 - ...(aObj.event === 'log' && aObj.type === THREEDS2_FULL && { result: aObj.result }), // only added if we have a log event of ThreeDS2 type - /** INFO */ - ...(aObj.event === 'info' && { type: aObj.type, target: aObj.target }), // info event - ...(aObj.event === 'info' && aObj.issuer && { issuer: aObj.issuer }), // relates to issuerLists - ...(aObj.event === 'info' && { isExpress: aObj.isExpress, expressPage: aObj.expressPage }), // relates to Plugins & detecting Express PMs - ...(aObj.event === 'info' && aObj.isStoredPaymentMethod && { isStoredPaymentMethod: aObj.isStoredPaymentMethod, brand: aObj.brand }), // only added if we have an info event about a storedPM - ...(aObj.event === 'info' && - aObj.type === ANALYTICS_VALIDATION_ERROR_STR && { - validationErrorCode: mapErrorCodesForAnalytics(aObj.validationErrorCode, aObj.target), - validationErrorMessage: aObj.validationErrorMessage - }), // only added if we have an info event describing a validation error - ...(aObj.configData && { configData: aObj.configData }), - /** All */ - ...(aObj.metadata && { metadata: aObj.metadata }) -}); + +export const createAnalyticsObject = (aObj: CreateAnalyticsObject): AnalyticsObject => { + return { + timestamp: String(getUTCTimestamp()), + component: aObj.component, + id: uuid(), + /** ERROR */ + ...(aObj.event === 'error' && { code: aObj.code, errorType: aObj.errorType, message: aObj.message }), // error event + /** LOG */ + ...(aObj.event === 'log' && { type: aObj.type, message: aObj.message }), // log event + ...(aObj.event === 'log' && (aObj.type === ANALYTICS_ACTION_STR || aObj.type === THREEDS2_FULL) && { subType: aObj.subtype }), // only added if we have a log event of Action type or ThreeDS2 + ...(aObj.event === 'log' && aObj.type === THREEDS2_FULL && { result: aObj.result }), // only added if we have a log event of ThreeDS2 type + /** INFO */ + ...(aObj.event === 'info' && { type: aObj.type, target: aObj.target }), // info event + ...(aObj.event === 'info' && aObj.issuer && { issuer: aObj.issuer }), // relates to issuerLists + ...(aObj.event === 'info' && { isExpress: aObj.isExpress, expressPage: aObj.expressPage }), // relates to Plugins & detecting Express PMs + ...(aObj.event === 'info' && aObj.isStoredPaymentMethod && { isStoredPaymentMethod: aObj.isStoredPaymentMethod, brand: aObj.brand }), // only added if we have an info event about a storedPM + ...(aObj.event === 'info' && + aObj.type === ANALYTICS_VALIDATION_ERROR_STR && { + validationErrorCode: mapErrorCodesForAnalytics(aObj.validationErrorCode, aObj.target), + validationErrorMessage: aObj.validationErrorMessage + }), // only added if we have an info event describing a validation error + ...(aObj.configData && { configData: aObj.configData }), + /** All */ + ...(aObj.metadata && { metadata: aObj.metadata }) + }; +}; const mapErrorCodesForAnalytics = (errorCode: string, target: string) => { // Some of the more generic error codes required combination with target to retrieve a specific code