Skip to content

Commit 93bb199

Browse files
devops-github-rudderstackweb-flowmaheshkuttyItsSudip
authored
chore(release): pull main into develop post release v1.126.0 (#5035)
👑 *An automated PR* --------- Co-authored-by: GitHub Actions <noreply@github.com> Co-authored-by: Mahesh Kutty <39219085+maheshkutty@users.noreply.github.com> Co-authored-by: Sudip Paul <67197965+ItsSudip@users.noreply.github.com>
1 parent b9b3411 commit 93bb199

File tree

14 files changed

+432
-22
lines changed

14 files changed

+432
-22
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,27 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
### [1.126.1](https://github.com/rudderlabs/rudder-transformer/compare/v1.126.0...v1.126.1) (2026-03-16)
6+
7+
8+
### Bug Fixes
9+
10+
* add validation to mixpanel for invalid context ([#5040](https://github.com/rudderlabs/rudder-transformer/issues/5040)) ([af1b713](https://github.com/rudderlabs/rudder-transformer/commit/af1b713ea1e8568436e5d517e410f8e42db62c46))
11+
* consider email from dataSoruce as well ([#5039](https://github.com/rudderlabs/rudder-transformer/issues/5039)) ([c18e1ac](https://github.com/rudderlabs/rudder-transformer/commit/c18e1acd1f5908a6391b5dc08930b08c0ed8a8ba))
12+
* **shopify:** extract email to context.traits for pixel app events ([#5041](https://github.com/rudderlabs/rudder-transformer/issues/5041)) ([839a19a](https://github.com/rudderlabs/rudder-transformer/commit/839a19a095707cab2fb2fb585ce3fa4447be452e))
13+
14+
## [1.126.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.125.0...v1.126.0) (2026-03-10)
15+
16+
17+
### Features
18+
19+
* update salesforce feature flag to be enabled on destinationDefinitionName and fix soql query ([#4998](https://github.com/rudderlabs/rudder-transformer/issues/4998)) ([40e31db](https://github.com/rudderlabs/rudder-transformer/commit/40e31db6b06ffaa9c9b7a3d5a46956f013f0a2c7))
20+
21+
22+
### Bug Fixes
23+
24+
* update team name from 'data-management' to 'pipelines' ([#4995](https://github.com/rudderlabs/rudder-transformer/issues/4995)) ([b94711c](https://github.com/rudderlabs/rudder-transformer/commit/b94711c428bf064509408a346857c55e0e857fcc))
25+
526
## [1.125.0](https://github.com/rudderlabs/rudder-transformer/compare/v1.124.4...v1.125.0) (2026-03-09)
627

728

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "rudder-transformer",
3-
"version": "1.125.0",
3+
"version": "1.126.1",
44
"description": "",
55
"homepage": "https://github.com/rudderlabs/rudder-transformer#readme",
66
"bugs": {

src/sources/facebook_lead_ads_native/hydrate.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import { hydrate } from './hydrate';
22
import * as network from '../../adapters/network';
3+
import logger from '../../logger';
34
import { SourceHydrationOutput, SourceHydrationRequest } from '../../types/sourceHydration';
45

56
jest.mock('../../adapters/network');
7+
jest.mock('../../logger', () => ({
8+
debug: jest.fn(),
9+
info: jest.fn(),
10+
error: jest.fn(),
11+
warn: jest.fn(),
12+
}));
613

714
const mockHttpGET = network.httpGET as jest.MockedFunction<typeof network.httpGET>;
815

@@ -389,6 +396,125 @@ describe('Facebook Lead Ads Hydration', () => {
389396
expect(result.batch[1].errorMessage).toBe('{"message":"Lead not found"}');
390397
});
391398

399+
describe('errorResponseHandler does not throw', () => {
400+
it('should throw and log schema when response has no error property', async () => {
401+
// Simulates a 500 from Facebook with no `error` in the body
402+
mockHttpGET.mockResolvedValue({
403+
success: false,
404+
response: {
405+
data: { some_field: 'some_value' },
406+
status: 500,
407+
},
408+
});
409+
410+
const input = createValidInput(['123456']);
411+
412+
await expect(hydrate(input)).rejects.toThrow(
413+
'Unexpected: errorResponseHandler did not throw for non-OK response',
414+
);
415+
expect(logger.error).toHaveBeenCalledWith(
416+
'[facebook_lead_ads_native] Non-OK response from Facebook API',
417+
{
418+
status: 500,
419+
response: JSON.stringify({
420+
type: 'object',
421+
properties: {
422+
response: { type: 'object', properties: { some_field: { type: 'string' } } },
423+
status: { type: 'number' },
424+
},
425+
}),
426+
},
427+
);
428+
});
429+
430+
it('should throw and log schema when response body is an empty object', async () => {
431+
mockHttpGET.mockResolvedValue({
432+
success: false,
433+
response: {
434+
data: {},
435+
status: 500,
436+
},
437+
});
438+
439+
const input = createValidInput(['123456']);
440+
441+
await expect(hydrate(input)).rejects.toThrow(
442+
'Unexpected: errorResponseHandler did not throw for non-OK response',
443+
);
444+
expect(logger.error).toHaveBeenCalledWith(
445+
'[facebook_lead_ads_native] Non-OK response from Facebook API',
446+
{
447+
status: 500,
448+
response: JSON.stringify({
449+
type: 'object',
450+
properties: {
451+
response: { type: 'object', properties: {} },
452+
status: { type: 'number' },
453+
},
454+
}),
455+
},
456+
);
457+
});
458+
459+
it('should throw and log schema when response body is a string', async () => {
460+
mockHttpGET.mockResolvedValue({
461+
success: false,
462+
response: {
463+
data: 'Internal Server Error',
464+
status: 500,
465+
},
466+
});
467+
468+
const input = createValidInput(['123456']);
469+
470+
await expect(hydrate(input)).rejects.toThrow(
471+
'Unexpected: errorResponseHandler did not throw for non-OK response',
472+
);
473+
expect(logger.error).toHaveBeenCalledWith(
474+
'[facebook_lead_ads_native] Non-OK response from Facebook API',
475+
{
476+
status: 500,
477+
response: JSON.stringify({
478+
type: 'object',
479+
properties: {
480+
response: { type: 'string' },
481+
status: { type: 'number' },
482+
},
483+
}),
484+
},
485+
);
486+
});
487+
488+
it('should throw and log schema when response error is null', async () => {
489+
mockHttpGET.mockResolvedValue({
490+
success: false,
491+
response: {
492+
data: { error: null },
493+
status: 500,
494+
},
495+
});
496+
497+
const input = createValidInput(['123456']);
498+
499+
await expect(hydrate(input)).rejects.toThrow(
500+
'Unexpected: errorResponseHandler did not throw for non-OK response',
501+
);
502+
expect(logger.error).toHaveBeenCalledWith(
503+
'[facebook_lead_ads_native] Non-OK response from Facebook API',
504+
{
505+
status: 500,
506+
response: JSON.stringify({
507+
type: 'object',
508+
properties: {
509+
response: { type: 'object', properties: { error: { type: 'null' } } },
510+
status: { type: 'number' },
511+
},
512+
}),
513+
},
514+
);
515+
});
516+
});
517+
392518
describe('Input validation', () => {
393519
const invalidInputTestCases: {
394520
name: string;

src/sources/facebook_lead_ads_native/hydrate.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { z } from 'zod';
2-
import { BaseError, formatZodError, InstrumentationError } from '@rudderstack/integrations-lib';
2+
import {
3+
BaseError,
4+
formatZodError,
5+
InstrumentationError,
6+
JsonSchemaGenerator,
7+
} from '@rudderstack/integrations-lib';
8+
import logger from '../../logger';
39
import { httpGET } from '../../adapters/network';
410
import { processAxiosResponse } from '../../adapters/utils/networkUtils';
511
import {
@@ -12,6 +18,7 @@ import {
1218
} from '../../types/sourceHydration';
1319
import { HTTP_STATUS_CODES } from '../../v0/util/constant';
1420
import { errorResponseHandler } from '../../v0/util/facebookUtils/networkHandler';
21+
import { isHttpStatusSuccess } from '../../v0/util';
1522

1623
// Complete schema
1724
const FacebookLeadAdsHydrationInputSchema = SourceHydrationRequestSchema.extend({
@@ -81,7 +88,7 @@ async function fetchLeadData(
8188

8289
const processedResponse = processAxiosResponse(clientResponse);
8390

84-
if (processedResponse.status === HTTP_STATUS_CODES.OK) {
91+
if (isHttpStatusSuccess(processedResponse.status)) {
8592
return {
8693
data: processedResponse.response,
8794
statusCode: HTTP_STATUS_CODES.OK,
@@ -103,6 +110,10 @@ async function fetchLeadData(
103110
}
104111
throw new Error(`Unexpected: unknown error type ${error}`);
105112
}
113+
logger.error(`[facebook_lead_ads_native] Non-OK response from Facebook API`, {
114+
status: processedResponse.status,
115+
response: JSON.stringify(JsonSchemaGenerator.generate(processedResponse)),
116+
});
106117
// This should never be reached since errorResponseHandler always throws for errors
107118
throw new Error('Unexpected: errorResponseHandler did not throw for non-OK response');
108119
}

src/sources/iterable/mapping.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[
22
{
3-
"sourceKeys": "email",
3+
"sourceKeys": ["email", "dataFields.email"],
44
"destKeys": "context.traits.email"
55
},
66
{

src/sources/iterable/transform.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ const isNonEmptyString = (val) => typeof val === 'string' && isDefinedAndNotNull
2020
*/
2121
function checkForRequiredFields(event) {
2222
if (
23-
(!isNonEmptyString(event.email) && !isNonEmptyString(event.userId)) ||
23+
(!isNonEmptyString(event.email) &&
24+
!isNonEmptyString(event.userId) &&
25+
!isNonEmptyString(event?.dataFields?.email)) ||
2426
!isNonEmptyString(event.eventName)
2527
) {
2628
throw new TransformationError('Unknown event type from Iterable');
@@ -55,7 +57,7 @@ function process(payload) {
5557
// Treating userId as unique identifier
5658
// If userId is not present, then generating it from email using md5 hash function
5759
if (!isDefinedAndNotNull(message.userId)) {
58-
message.userId = md5(event.email);
60+
message.userId = md5(message.context.traits.email);
5961
}
6062

6163
return message;

src/sources/shopify/webpixelTransformations/pixelTransform.test.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,86 @@ describe('pixelTransform', () => {
9797
expect(result).toMatchObject(expected);
9898
});
9999

100+
it('should extract email from checkout data to context.traits.email for checkout_completed', async () => {
101+
RedisDB.getVal.mockResolvedValue(null);
102+
RedisDB.setVal.mockResolvedValue(null);
103+
104+
const input = {
105+
name: PIXEL_EVENT_TOPICS.CHECKOUT_COMPLETED,
106+
clientId: 'test-client',
107+
id: 'test-id',
108+
timestamp: '2024-01-01',
109+
context: {
110+
document: {
111+
location: {
112+
pathname: '/checkout/cn/abc123',
113+
},
114+
},
115+
},
116+
data: {
117+
checkout: {
118+
email: 'user@example.com',
119+
token: 'abc123',
120+
lineItems: [],
121+
totalPrice: { amount: 100 },
122+
currencyCode: 'USD',
123+
},
124+
},
125+
query_parameters: { writeKey: 'test' },
126+
};
127+
128+
const result = await processPixelWebEvents(input);
129+
expect(result.context.traits.email).toBe('user@example.com');
130+
});
131+
132+
it('should not set context.traits.email when checkout email is empty', async () => {
133+
RedisDB.getVal.mockResolvedValue(null);
134+
RedisDB.setVal.mockResolvedValue(null);
135+
136+
const input = {
137+
name: PIXEL_EVENT_TOPICS.CHECKOUT_STARTED,
138+
clientId: 'test-client',
139+
id: 'test-id',
140+
timestamp: '2024-01-01',
141+
context: {
142+
document: {
143+
location: {
144+
pathname: '/checkout/cn/abc123',
145+
},
146+
},
147+
},
148+
data: {
149+
checkout: {
150+
email: '',
151+
token: 'abc123',
152+
lineItems: [],
153+
totalPrice: { amount: 100 },
154+
currencyCode: 'USD',
155+
},
156+
},
157+
query_parameters: { writeKey: 'test' },
158+
};
159+
160+
const result = await processPixelWebEvents(input);
161+
expect(result.context.traits?.email).toBeUndefined();
162+
});
163+
164+
it('should not set context.traits.email for non-checkout events', async () => {
165+
RedisDB.getVal.mockResolvedValue(null);
166+
167+
const input = {
168+
name: PIXEL_EVENT_TOPICS.PAGE_VIEWED,
169+
clientId: 'test-client',
170+
id: 'test-id',
171+
timestamp: '2024-01-01',
172+
context: {},
173+
query_parameters: { writeKey: 'test' },
174+
};
175+
176+
const result = await processPixelWebEvents(input);
177+
expect(result.context.traits?.email).toBeUndefined();
178+
});
179+
100180
it('should attach userId from Redis if available', async () => {
101181
RedisDB.getVal.mockResolvedValue({ userId: 'test-user' });
102182

0 commit comments

Comments
 (0)