Skip to content

Commit

Permalink
refactor(analytics): send redirect error event
Browse files Browse the repository at this point in the history
  • Loading branch information
longyulongyu committed Nov 19, 2024
1 parent ca44cd9 commit 2e954c5
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 40 deletions.
19 changes: 18 additions & 1 deletion packages/lib/src/components/Redirect/Redirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<RedirectConfiguration> {
public static type = TxVariants.redirect;
Expand All @@ -23,6 +24,15 @@ class RedirectElement extends UIElement<RedirectConfiguration> {
};
}

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;
}
Expand All @@ -33,7 +43,14 @@ class RedirectElement extends UIElement<RedirectConfiguration> {

render() {
if (this.props.url && this.props.method) {
return <RedirectShopper url={this.props.url} {...this.props} onActionHandled={this.onActionHandled} />;
return (
<RedirectShopper
url={this.props.url}
{...this.props}
onActionHandled={this.onActionHandled}
onRedirectError={this.handleRedirectError}
/>
);
}

if (this.props.showPayButton) {
Expand Down
20 changes: 14 additions & 6 deletions packages/lib/src/components/internal/UIElement/UIElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,25 @@ export abstract class UIElement<P extends UIElementProps = UIElementProps> 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();
Expand Down
10 changes: 3 additions & 7 deletions packages/lib/src/core/Analytics/EventsQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const EventsQueue = ({ analyticsContext, clientKey, analyticsPath }: EventQueueP
logs: []
};

const runQueue = (checkoutAttemptId: string): Promise<any> => {
const runQueue = async (checkoutAttemptId: string): Promise<any> => {
if (!caActions.info.length && !caActions.logs.length && !caActions.errors.length) {
return Promise.resolve(null);
}
Expand All @@ -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;
Expand All @@ -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);
},
Expand All @@ -66,8 +64,6 @@ const EventsQueue = ({ analyticsContext, clientKey, analyticsPath }: EventQueueP
// Expose getter for testing purposes
getQueue: () => caActions
};

return eqModule;
};

export default EventsQueue;
3 changes: 2 additions & 1 deletion packages/lib/src/core/Analytics/analyticsPreProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions packages/lib/src/core/Analytics/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
4 changes: 3 additions & 1 deletion packages/lib/src/core/Analytics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ export type CreateAnalyticsEventObject = {

export type EventQueueProps = Pick<AnalyticsConfig, 'analyticsContext' | 'clientKey'> & { analyticsPath: string };

export type SendAnalyticsObject = Omit<AnalyticsObject, 'timestamp' | 'component' | 'id'>;
export interface SendAnalyticsObject extends Omit<AnalyticsObject, 'timestamp' | 'id' | 'component'> {
component?: string;
}

export type FieldErrorAnalyticsObject = {
fieldType: string;
Expand Down
51 changes: 27 additions & 24 deletions packages/lib/src/core/Analytics/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2e954c5

Please sign in to comment.