Skip to content

Commit

Permalink
Merge branch 'develop' into gaec.refactor-proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
mihir-4116 authored Mar 15, 2024
2 parents d966bd3 + 6330888 commit 935fb7f
Show file tree
Hide file tree
Showing 35 changed files with 7,574 additions and 7,935 deletions.
2 changes: 1 addition & 1 deletion src/controllers/trackingPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class TrackingPlanController {
const events = ctx.request.body;
const requestSize = Number(ctx.request.get('content-length'));
const reqParams = ctx.request.query;
const response = await TrackingPlanservice.validateTrackingPlan(events, requestSize, reqParams);
const response = await TrackingPlanservice.validate(events, requestSize, reqParams);
ctx.body = response.body;
ControllerUtility.postProcess(ctx, response.status);
return ctx;
Expand Down
113 changes: 56 additions & 57 deletions src/services/trackingPlan.ts
Original file line number Diff line number Diff line change
@@ -1,88 +1,87 @@
import logger from '../logger';
import { RetryRequestError, RespStatusError, constructValidationErrors } from '../util/utils';
import { getMetadata } from '../v0/util';
import { getMetadata, getTrackingPlanMetadata } from '../v0/util';
import eventValidator from '../util/eventValidation';
import stats from '../util/stats';
import { HTTP_STATUS_CODES } from '../v0/util/constant';

export class TrackingPlanservice {
public static async validateTrackingPlan(events, requestSize, reqParams) {
const requestStartTime = new Date();
public static async validate(events, requestSize, reqParams) {
const startTime = Date.now();
const respList: any[] = [];
const metaTags = events[0].metadata ? getMetadata(events[0].metadata) : {};
const metaTags = events.length && events[0].metadata ? getMetadata(events[0].metadata) : {};
const tpTags: any =
events.length && events[0].metadata ? getTrackingPlanMetadata(events[0].metadata) : {};
let ctxStatusCode = 200;

for (let i = 0; i < events.length; i++) {
let eventValidationResponse: any;
let exceptionOccured = false;
const eventStartTime = Date.now();
const event = events[i];
const eventStartTime = new Date();

try {
const parsedEvent = event;
parsedEvent.request = { query: reqParams };
const hv = await eventValidator.handleValidation(parsedEvent);
if (hv['dropEvent']) {
respList.push({
output: event.message,
metadata: event.metadata,
statusCode: 400,
validationErrors: hv['validationErrors'],
error: JSON.stringify(constructValidationErrors(hv['validationErrors'])),
});
stats.counter('tp_violation_type', 1, {
violationType: hv['violationType'],
...metaTags,
});
} else {
respList.push({
output: event.message,
metadata: event.metadata,
statusCode: 200,
validationErrors: hv['validationErrors'],
error: JSON.stringify(constructValidationErrors(hv['validationErrors'])),
});
stats.counter('tp_propagated_events', 1, {
...metaTags,
});
}
} catch (error) {
const errMessage = `Error occurred while validating : ${error}`;
logger.error(errMessage);
let status = 200;
event.request = { query: reqParams };
const validatedEvent = await eventValidator.handleValidation(event);
eventValidationResponse = {
output: event.message,
metadata: event.metadata,
statusCode: validatedEvent['dropEvent']
? HTTP_STATUS_CODES.BAD_REQUEST
: HTTP_STATUS_CODES.OK,
validationErrors: validatedEvent['validationErrors'],
error: JSON.stringify(constructValidationErrors(validatedEvent['validationErrors'])),
};
} catch (error: any) {
logger.debug(
`Error occurred while validating event`,
'event',
`${event.message?.event}::${event.message?.type}`,
'trackingPlan',
`${tpTags?.trackingPlanId}`,
'error',
error.message,
);

exceptionOccured = true;
// no need to process further if
// we have error of retry request error
if (error instanceof RetryRequestError) {
ctxStatusCode = error.statusCode;
break;
}
if (error instanceof RespStatusError) {
status = error.statusCode;
}
respList.push({

eventValidationResponse = {
output: event.message,
metadata: event.metadata,
statusCode: status,
statusCode: error instanceof RespStatusError ? error.statusCode : HTTP_STATUS_CODES.OK,
validationErrors: [],
error: errMessage,
});
stats.counter('tp_errors', 1, {
...metaTags,
workspaceId: event.metadata?.workspaceId,
trackingPlanId: event.metadata?.trackingPlanId,
});
error: `Error occurred while validating: ${error}`,
};
} finally {
stats.timing('tp_event_latency', eventStartTime, {
// finally on every event, we need to
// capture the information related to the validates event
stats.timing('tp_event_validation_latency', eventStartTime, {
...metaTags,
...tpTags,
status: eventValidationResponse.statusCode,
exception: exceptionOccured,
});
}
}

stats.counter('tp_events_count', events.length, {
...metaTags,
});
respList.push(eventValidationResponse);
}

stats.histogram('tp_request_size', requestSize, {
stats.histogram('tp_batch_size', requestSize, {
...metaTags,
...tpTags,
});

stats.timing('tp_request_latency', requestStartTime, {
// capture overall function latency
// with metadata tags
stats.histogram('tp_batch_validation_latency', (Date.now() - startTime) / 1000, {
...metaTags,
workspaceId: events[0]?.metadata?.workspaceId,
trackingPlanId: events[0]?.metadata?.trackingPlanId,
...tpTags,
});

return { body: respList, status: ctxStatusCode };
Expand Down
30 changes: 25 additions & 5 deletions src/util/prometheus.js
Original file line number Diff line number Diff line change
Expand Up @@ -590,14 +590,34 @@ class Prometheus {
labelNames: ['method', 'route', 'code'],
},
{
name: 'tp_request_size',
help: 'tp_request_size',
name: 'tp_batch_size',
help: 'Size of batch of events for tracking plan validation',
type: 'histogram',
labelNames: ['sourceType', 'destinationType', 'k8_namespace'],
labelNames: [
'sourceType',
'destinationType',
'k8_namespace',
'workspaceId',
'trackingPlanId',
],
},
{
name: 'tp_event_validation_latency',
help: 'Latency of validating tracking plan at event level',
type: 'histogram',
labelNames: [
'sourceType',
'destinationType',
'k8_namespace',
'workspaceId',
'trackingPlanId',
'status',
'exception',
],
},
{
name: 'tp_request_latency',
help: 'tp_request_latency',
name: 'tp_batch_validation_latency',
help: 'Latency of validating tracking plan at batch level',
type: 'histogram',
labelNames: [
'sourceType',
Expand Down
28 changes: 28 additions & 0 deletions src/v0/destinations/facebook_pixel/transform.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ const getTestMessage = () => {
return message;
};

const getTestMessageWithoutProductIdAndCategory = () => {
let message = {
properties: {
currency: 'CAD',
quantity: 1,
price: 24.75,
value: 30,
name: 'my product 1',
testDimension: true,
testMetric: true,
position: 4.5,
query: 'HDMI Cable',
},
};
return message;
};

const getTestCategoryToContent = () => {
let categoryToContent = [
{
Expand Down Expand Up @@ -52,6 +69,17 @@ describe('Unit test cases for facebook_pixel handle search', () => {
expect(handleSearch(getTestMessage())).toEqual(expectedOutput);
});

it('should return content with content_ids and content fields as empty array', async () => {
const expectedOutput = {
content_ids: [],
content_category: '',
value: 30,
search_string: 'HDMI Cable',
contents: [],
};
expect(handleSearch(getTestMessageWithoutProductIdAndCategory())).toEqual(expectedOutput);
});

it("mapping 'product_id' with contentId", async () => {
let message = getTestMessage();
message.properties.product_id = 'prd-123';
Expand Down
154 changes: 154 additions & 0 deletions src/v0/destinations/facebook_pixel/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
const { InstrumentationError } = require('@rudderstack/integrations-lib');
const { getActionSource, formatRevenue, getCategoryFromEvent } = require('./utils');
const { CONFIG_CATEGORIES, OTHER_STANDARD_EVENTS } = require('./config');

describe('Test Facebook Pixel Utils', () => {
describe('getActionSource', () => {
// Returns 'other' if payload.action_source is not defined and channel is neither 'web' nor 'mobile'
it('should return "other" when payload.action_source is not defined and channel is neither "web" nor "mobile"', () => {
const payload = {};
const channel = 'email';
const result = getActionSource(payload, channel);
expect(result).toBe('other');
});

// Returns payload.action_source if it is defined and is a valid value from ACTION_SOURCES_VALUES
it('should return payload.action_source when it is defined and is a valid value from ACTION_SOURCES_VALUES', () => {
const payload = { action_source: 'website' };
const channel = 'email';
const result = getActionSource(payload, channel);
expect(result).toBe('website');
});

// Returns 'website' if channel is 'web' and payload.action_source is not defined
it('should return "website" when channel is "web" and payload.action_source is not defined', () => {
const payload = {};
const channel = 'web';
const result = getActionSource(payload, channel);
expect(result).toBe('website');
});

// Throws an InstrumentationError if payload.action_source is defined but not a valid value from ACTION_SOURCES_VALUES
it('should throw an InstrumentationError when payload.action_source is defined but not a valid value from ACTION_SOURCES_VALUES', () => {
const payload = { action_source: 'invalid' };
const channel = 'email';
expect(() => {
getActionSource(payload, channel);
}).toThrow(InstrumentationError);
});
});

describe('formatRevenue', () => {
// Returns a number with two decimal places when passed a valid revenue value.
it('should return a number with two decimal places when passed a valid revenue value', () => {
const revenue = '100.50';
const formattedRevenue = formatRevenue(revenue);
expect(formattedRevenue).toBe(100.5);
});

// Returns 0 when passed a null revenue value.
it('should return 0 when passed a null revenue value', () => {
const revenue = null;
const formattedRevenue = formatRevenue(revenue);
expect(formattedRevenue).toBe(0);
});

// Returns 0 when passed an undefined revenue value.
it('should return 0 when passed an undefined revenue value', () => {
const revenue = undefined;
const formattedRevenue = formatRevenue(revenue);
expect(formattedRevenue).toBe(0);
});

// Throws an InstrumentationError when passed a non-numeric string revenue value.
it('should throw an InstrumentationError when passed a non-numeric string revenue value', () => {
const revenue = 'abc';
expect(() => {
formatRevenue(revenue);
}).toThrow(InstrumentationError);
});

// Returns a number with two decimal places when passed a numeric string revenue value with more than two decimal places.
it('should return a number with two decimal places when passed a numeric string revenue value with more than two decimal places', () => {
const revenue = '100.555';
const formattedRevenue = formatRevenue(revenue);
expect(formattedRevenue).toBe(100.56);
});

// Returns a number with two decimal places when passed a numeric value with more than two decimal places.
it('should return a number with two decimal places when passed a numeric value with more than two decimal places', () => {
const revenue = 100.555;
const formattedRevenue = formatRevenue(revenue);
expect(formattedRevenue).toBe(100.56);
});
});

describe('getCategoryFromEvent', () => {
// The function correctly maps the eventName to its corresponding category.
it('should correctly map the eventName to its corresponding category', () => {
const eventName = CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.type;
const result = getCategoryFromEvent(eventName);
expect(result).toEqual(CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED);
});

// The function returns the correct category for a given eventName.
it('should return the correct category for a given eventName', () => {
const eventName = CONFIG_CATEGORIES.PRODUCT_VIEWED.type;
const result = getCategoryFromEvent(eventName);
expect(result).toEqual(CONFIG_CATEGORIES.PRODUCT_VIEWED);
});

// The function returns the default category if the eventName is not recognized.
it('should return the default category if the eventName is not recognized', () => {
const eventName = 'unknownEvent';
const result = getCategoryFromEvent(eventName);
expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
});

// The function handles null or undefined eventName inputs.
it('should handle null or undefined eventName inputs', () => {
const eventName = null;
const result = getCategoryFromEvent(eventName);
expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
});

// The function handles empty string eventName inputs.
it('should handle empty string eventName inputs', () => {
const eventName = '';
const result = getCategoryFromEvent(eventName);
expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
});

// The function handles eventName inputs that are not strings.
it('should handle eventName inputs that are not strings', () => {
const eventName = 123;
const result = getCategoryFromEvent(eventName);
expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
});

// The function handles multiple eventNames that map to the same category.
it('should correctly map multiple eventNames to the same category', () => {
const eventName1 = CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.type;
const eventName2 = CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED.eventName;
const result1 = getCategoryFromEvent(eventName1);
const result2 = getCategoryFromEvent(eventName2);
expect(result1).toEqual(CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED);
expect(result2).toEqual(CONFIG_CATEGORIES.PRODUCT_LIST_VIEWED);
});

// The function handles eventNames that are included in the OTHER_STANDARD_EVENTS list.
it('should correctly handle eventNames included in the OTHER_STANDARD_EVENTS list', () => {
const eventName = OTHER_STANDARD_EVENTS[0];
const result = getCategoryFromEvent(eventName);
expect(result).toEqual(CONFIG_CATEGORIES.OTHER_STANDARD);
expect(result.eventName).toEqual(eventName);
});

// The function handles eventNames that are not recognized and not in the OTHER_STANDARD_EVENTS list.
it('should correctly handle unrecognized eventNames', () => {
const eventName = 'unrecognizedEvent';
const result = getCategoryFromEvent(eventName);
expect(result).toEqual(CONFIG_CATEGORIES.SIMPLE_TRACK);
});
});
});
Loading

0 comments on commit 935fb7f

Please sign in to comment.