Skip to content

Commit

Permalink
fix: adding type definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
shrouti1507 committed Jan 3, 2025
1 parent 9ce4575 commit de1bb5c
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 306 deletions.
6 changes: 1 addition & 5 deletions src/v0/destinations/iterable/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const ConfigCategory = {
name: 'IterableIdentifyConfig',
action: 'identify',
endpoint: `users/update`,
bulkEndpoint: 'users/bulkUpdate',
},
PAGE: {
name: 'IterablePageConfig',
Expand All @@ -36,7 +35,6 @@ const ConfigCategory = {
name: 'IterableTrackConfig',
action: 'track',
endpoint: `events/track`,
bulkEndpoint: 'events/trackBulk',
},
TRACK_PURCHASE: {
name: 'IterableTrackPurchaseConfig',
Expand Down Expand Up @@ -78,9 +76,7 @@ const constructEndpoint = (dataCenter, category) => {
return `${baseUrl}${category.endpoint}`;
};

const BULK_ENDPOINTS = Object.values(ConfigCategory)
.filter((config) => config.bulkEndpoint)
.map((config) => `/api/${config.bulkEndpoint}`);
const BULK_ENDPOINTS = ['/api/users/bulkUpdate', '/api/events/trackBulk'];

const IDENTIFY_MAX_BATCH_SIZE = 1000;
const IDENTIFY_MAX_BODY_SIZE_IN_BYTES = 4000000;
Expand Down
82 changes: 0 additions & 82 deletions src/v0/destinations/iterable/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ const {
TRACK_MAX_BATCH_SIZE,
IDENTIFY_MAX_BATCH_SIZE,
IDENTIFY_MAX_BODY_SIZE_IN_BYTES,
// API_RESPONSE_PATHS,
constructEndpoint,
ITERABLE_RESPONSE_USER_ID_PATHS,
ITERABLE_RESPONSE_EMAIL_PATHS,
} = require('./config');
const { JSON_MIME_TYPE } = require('../../util/constant');
const { EventType, MappedToDestinationKey } = require('../../../constants');
Expand Down Expand Up @@ -748,84 +745,6 @@ const filterEventsAndPrepareBatchRequests = (transformedEvents) => {
return prepareBatchRequests(filteredEvents);
};

/**
* Checks if a value is present in a response array based on a given path.
* @param {Object} response - The response object to search within.
* @param {string} path - The path to the response array.
* @param {any} value - The value to check for in the array.
* @returns {boolean} - True if the value is in the array, otherwise false.
*/
const isValueInResponseArray = (response, path, value) => {
const respArr = get(response, path);
return Array.isArray(respArr) && respArr.includes(value);
};

/**
* Determines if an event should be aborted based on the response from a destination
* and extracts an error message if applicable.
* ref:
* 1) https://api.iterable.com/api/docs#users_updateEmail
* 2) https://api.iterable.com/api/docs#events_track
* 3) https://api.iterable.com/api/docs#users_bulkUpdateUser
* 4) https://api.iterable.com/api/docs#events_trackBulk
* 5) https://api.iterable.com/api/docs#catalogs_bulkUpdateCatalogItems
* 6) https://api.iterable.com/api/docs#users_registerDeviceToken
* 7) https://api.iterable.com/api/docs#users_registerBrowserToken
* 8) https://api.iterable.com/api/docs#commerce_trackPurchase
* 9) https://api.iterable.com/api/docs#commerce_updateCart
*
* @param {Object} event - The event object containing various event properties.
* @param {Object} destinationResponse - The response object from the destination.
* @returns {Object} An object containing a boolean `isAbortable` indicating if the event
* should be aborted, and an `errorMsg` string with the error message if applicable.
*/
const checkIfEventIsAbortableAndExtractErrorMessage = (event, destinationResponse) => {
const { failCount } = destinationResponse.response;

if (failCount === 0) {
return { isAbortable: false, errorMsg: '' };
}

const eventValues = {
email: event.email,
userId: event.userId,
eventName: event.eventName,
};

let errorMsg = '';
const userIdMatchPath = ITERABLE_RESPONSE_USER_ID_PATHS.filter((userIdPath) =>
isValueInResponseArray(destinationResponse.response, userIdPath, eventValues.userId),
);
if (userIdMatchPath.length > 0) {
errorMsg += `userId error:"${eventValues.userId}" in "${userIdMatchPath}".`;
}

const emailMatchPath = ITERABLE_RESPONSE_EMAIL_PATHS.filter((emailPath) =>
isValueInResponseArray(destinationResponse.response, emailPath, eventValues.email),
);

if (emailMatchPath.length > 0) {
errorMsg += `email error:"${eventValues.email}" in "${emailMatchPath}".`;
}

const eventNameMatchPath = ['disallowedEventNames'].filter((eventNamePath) =>
isValueInResponseArray(destinationResponse.response, eventNamePath, eventValues.eventName),
);

if (eventNameMatchPath.length > 0) {
errorMsg += `eventName error:"${eventValues.eventName}" in "${eventNameMatchPath}".`;
}

if (errorMsg) {
return {
isAbortable: true,
errorMsg,
};
}

return { isAbortable: false, errorMsg: '' };
};

module.exports = {
getCatalogEndpoint,
hasMultipleResponses,
Expand All @@ -840,6 +759,5 @@ module.exports = {
filterEventsAndPrepareBatchRequests,
registerDeviceTokenEventPayloadBuilder,
registerBrowserTokenEventPayloadBuilder,
checkIfEventIsAbortableAndExtractErrorMessage,
getCategoryWithEndpoint,
};
177 changes: 0 additions & 177 deletions src/v0/destinations/iterable/util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ const {
updateUserEventPayloadBuilder,
registerDeviceTokenEventPayloadBuilder,
registerBrowserTokenEventPayloadBuilder,
checkIfEventIsAbortableAndExtractErrorMessage,
} = require('./util');

const { ConfigCategory } = require('./config');

const getTestMessage = () => {
Expand Down Expand Up @@ -800,179 +798,4 @@ describe('iterable utils test', () => {
);
});
});
describe('checkIfEventIsAbortableAndExtractErrorMessage', () => {
// Returns non-abortable and empty error message when failCount is 0
it('should return non-abortable and empty error message when failCount is 0', () => {
const event = {
email: 'test@example.com',
userId: 'user123',
eventName: 'testEvent',
};
const destinationResponse = {
response: {
failCount: 0,
},
};

const result = checkIfEventIsAbortableAndExtractErrorMessage(event, destinationResponse);
expect(result).toEqual({ isAbortable: false, errorMsg: '' });
});

// Handles undefined or null event fields gracefully
it('should handle undefined or null event fields gracefully', () => {
const event = {
email: null,
userId: undefined,
eventName: 'testEvent',
};
const destinationResponse = {
response: {
failCount: 1,
invalidEmails: ['test@example.com'],
},
};
const result = checkIfEventIsAbortableAndExtractErrorMessage(event, destinationResponse);
expect(result).toEqual({ isAbortable: false, errorMsg: '' });
});

// Handles events with all expected fields present
it('should handle events with all expected fields present and return non-abortable when no match', () => {
const event = {
email: 'test@example.com',
userId: 'user123',
eventName: 'purchase',
id: 'event123',
createdAt: '2023-10-01T00:00:00Z',
campaignId: 'campaign123',
templateId: 'template123',
createNewFields: true,
dataFields: { field1: 'value1' },
};

const destinationResponse = {
response: {
failCount: 1,
invalidEmails: ['another@example.com'],
},
};

const result = checkIfEventIsAbortableAndExtractErrorMessage(event, destinationResponse);

expect(result.isAbortable).toBe(false);
expect(result.errorMsg).toBe('');
});

// Returns appropriate error message for abortable event

it('should find the right value for which it should fail and passes otherwise for emails', () => {
const event = {
email: 'test',
userId: 'user123',
eventName: 'purchase',
dataFields: { customField1: 'value1', customField2: 'value2' },
};
const destinationResponse = {
response: {
failCount: 1,
failedUpdates: {
invalidEmails: ['test'],
},
},
};
const result = checkIfEventIsAbortableAndExtractErrorMessage(event, destinationResponse);
expect(result).toEqual({
isAbortable: true,
errorMsg: 'email error:"test" in "failedUpdates.invalidEmails".',
});
});

it('should find the right value for which it should fail', () => {
const event = {
email: 'test@gmail.com',
userId: 'user123',
eventName: 'purchase',
dataFields: { customField1: 'test', customField2: 'value2' },
};
const destinationResponse = {
response: {
failCount: 1,
failedUpdates: {
invalidEmails: ['test'],
},
},
};
const result = checkIfEventIsAbortableAndExtractErrorMessage(event, destinationResponse);
expect(result.isAbortable).toBe(false);
expect(result.errorMsg).toBe('');
});

it('should find all the matching paths it failed for and curate error message', () => {
const event = {
email: 'test',
userId: 'user123',
eventName: 'purchase',
dataFields: { customField1: 'test', customField2: 'value2' },
};
const destinationResponse = {
response: {
failCount: 1,
invalidEmails: ['test'],
failedUpdates: {
invalidEmails: ['test'],
conflictEmails: ['test'],
},
},
};
const result = checkIfEventIsAbortableAndExtractErrorMessage(event, destinationResponse);
expect(result.isAbortable).toBe(true);
expect(result.errorMsg).toBe(
'email error:"test" in "invalidEmails,failedUpdates.invalidEmails,failedUpdates.conflictEmails".',
);
});

it('should find the right value for which it should fail and passes otherwise for userIds', () => {
const event = {
email: 'test',
userId: 'user123',
eventName: 'purchase',
dataFields: { customField1: 'value1', customField2: 'value2' },
};
const destinationResponse = {
response: {
failCount: 1,
failedUpdates: {
invalidUserIds: ['user123'],
},
},
};
const result = checkIfEventIsAbortableAndExtractErrorMessage(event, destinationResponse);
expect(result).toEqual({
isAbortable: true,
errorMsg: 'userId error:"user123" in "failedUpdates.invalidUserIds".',
});
});

it('should find the right value for which it should fail and passes otherwise for disallowed events', () => {
const event = {
email: 'test',
userId: 'user123',
eventName: 'purchase',
dataFields: { customField1: 'value1', customField2: 'value2' },
};
const destinationResponse = {
response: {
failCount: 1,
disallowedEventNames: ['purchase'],
failedUpdates: {
invalidUserIds: [],
},
},
};
const result = checkIfEventIsAbortableAndExtractErrorMessage(event, destinationResponse);
expect(result).toEqual({
isAbortable: true,
errorMsg: 'eventName error:"purchase" in "disallowedEventNames".',
});
});
});
});
9 changes: 2 additions & 7 deletions src/v1/destinations/iterable/networkHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import { processAxiosResponse } from '../../../adapters/utils/networkUtils';
import { BULK_ENDPOINTS } from '../../../v0/destinations/iterable/config';
import { GenericStrategy } from './strategies/generic';
import { TrackIdentifyStrategy } from './strategies/track-identify';

type ResponseParams = {
destinationRequest: {
endpoint: string;
};
};
import { GenericProxyHandlerInput } from './types';

const strategyRegistry: { [key: string]: any } = {
[TrackIdentifyStrategy.name]: new TrackIdentifyStrategy(),
Expand All @@ -22,7 +17,7 @@ const getResponseStrategy = (endpoint: string) => {
return strategyRegistry[GenericStrategy.name];
};

const responseHandler = (responseParams: ResponseParams) => {
const responseHandler = (responseParams: GenericProxyHandlerInput) => {
const { destinationRequest } = responseParams;
const strategy = getResponseStrategy(destinationRequest.endpoint);
return strategy.handleResponse(responseParams);
Expand Down
20 changes: 12 additions & 8 deletions src/v1/destinations/iterable/strategies/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,24 @@ import { TAG_NAMES } from '@rudderstack/integrations-lib';
import { getDynamicErrorType } from '../../../../adapters/utils/networkUtils';
import { isHttpStatusSuccess } from '../../../../v0/util';
import { TransformerProxyError } from '../../../../v0/util/errorTypes';
import { DestinationResponse, ResponseParams } from '../types';
import { GenericProxyHandlerInput } from '../types';

// Base strategy is the base class for all strategies in Iterable destination
class BaseStrategy {
handleResponse(responseParams: { destinationResponse: DestinationResponse }): void {
handleResponse(responseParams: GenericProxyHandlerInput): void {
const { destinationResponse } = responseParams;
const { status } = destinationResponse;

if (!isHttpStatusSuccess(status)) {
return this.handleError({
destinationResponse,
rudderJobMetadata: [],
});
return this.handleError(responseParams);
}

return this.handleSuccess(responseParams);
}

handleError(responseParams: ResponseParams): void {
handleError(responseParams: GenericProxyHandlerInput): void {
const { destinationResponse, rudderJobMetadata } = responseParams;
const { response, status } = destinationResponse;
// @ts-expect-error: not sure if `response.message` is correct or needed
const responseMessage = response.params || response.msg || response.message;
const errorMessage = JSON.stringify(responseMessage) || 'unknown error format';

Expand All @@ -49,3 +45,11 @@ class BaseStrategy {
}

export { BaseStrategy };

// TODO:
/**
* 1) fix return types appropriately
* 2) use pre-declared types, rather than adding our own types
* 3) no need for unnecessary refactors
* 4) add different implementations for handle Errors
*/
Loading

0 comments on commit de1bb5c

Please sign in to comment.