Skip to content

Commit ba474db

Browse files
committed
[FSSDK-11207] add multi region support for logx events
1 parent 41fed0d commit ba474db

File tree

7 files changed

+199
-3
lines changed

7 files changed

+199
-3
lines changed

lib/event_processor/event_builder/log_event.spec.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ import {
1919
buildConversionEventV1,
2020
buildImpressionEventV1,
2121
makeEventBatch,
22+
buildLogEvent,
2223
} from './log_event';
2324

24-
import { ImpressionEvent, ConversionEvent } from './user_event';
25+
import { ImpressionEvent, ConversionEvent, UserEvent } from './user_event';
26+
import { Region } from '../../project_config/project_config';
27+
2528

2629
describe('buildImpressionEventV1', () => {
2730
it('should build an ImpressionEventV1 when experiment and variation are defined', () => {
@@ -810,3 +813,64 @@ describe('makeEventBatch', () => {
810813
})
811814
})
812815

816+
describe('buildLogEvent', () => {
817+
it('should select the correct URL based on the event context region', () => {
818+
const baseEvent: ImpressionEvent = {
819+
type: 'impression',
820+
timestamp: 69,
821+
uuid: 'uuid',
822+
context: {
823+
accountId: 'accountId',
824+
projectId: 'projectId',
825+
clientName: 'node-sdk',
826+
clientVersion: '3.0.0',
827+
revision: 'revision',
828+
botFiltering: true,
829+
anonymizeIP: true
830+
},
831+
user: {
832+
id: 'userId',
833+
attributes: []
834+
},
835+
layer: {
836+
id: 'layerId'
837+
},
838+
experiment: {
839+
id: 'expId',
840+
key: 'expKey'
841+
},
842+
variation: {
843+
id: 'varId',
844+
key: 'varKey'
845+
},
846+
ruleKey: 'expKey',
847+
flagKey: 'flagKey1',
848+
ruleType: 'experiment',
849+
enabled: true
850+
};
851+
852+
// Test for US region
853+
const usEvent = {
854+
...baseEvent,
855+
context: {
856+
...baseEvent.context,
857+
region: 'US' as Region
858+
}
859+
};
860+
861+
const usResult = buildLogEvent([usEvent]);
862+
expect(usResult.url).toBe('https://logx.optimizely.com/v1/events');
863+
864+
// Test for EU region
865+
const euEvent = {
866+
...baseEvent,
867+
context: {
868+
...baseEvent.context,
869+
region: 'EU' as Region
870+
}
871+
};
872+
873+
const euResult = buildLogEvent([euEvent]);
874+
expect(euResult.url).toBe('https://eu.logx.optimizely.com/v1/events');
875+
});
876+
});

lib/event_processor/event_builder/log_event.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,16 @@ import { CONTROL_ATTRIBUTES } from '../../utils/enums';
1919

2020
import { LogEvent } from '../event_dispatcher/event_dispatcher';
2121
import { EventTags } from '../../shared_types';
22+
import { Region } from '../../project_config/project_config';
2223

2324
const ACTIVATE_EVENT_KEY = 'campaign_activated'
2425
const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
2526

27+
export const logxEndpoint: Record<Region, string> = {
28+
US: 'https://logx.optimizely.com/v1/events',
29+
EU: 'https://eu.logx.optimizely.com/v1/events',
30+
}
31+
2632
export type EventBatch = {
2733
account_id: string
2834
project_id: string
@@ -257,8 +263,11 @@ export function buildConversionEventV1(data: ConversionEvent): EventBatch {
257263
}
258264

259265
export function buildLogEvent(events: UserEvent[]): LogEvent {
266+
const region = events[0]?.context.region || 'US';
267+
const url = logxEndpoint[region];
268+
260269
return {
261-
url: 'https://logx.optimizely.com/v1/events',
270+
url,
262271
httpVerb: 'POST',
263272
params: makeEventBatch(events),
264273
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { buildImpressionEvent, buildConversionEvent } from './user_event';
3+
import { createProjectConfig, ProjectConfig } from '../../project_config/project_config';
4+
import { DecisionObj } from '../../core/decision_service';
5+
import testData from '../../tests/test_data';
6+
7+
describe('buildImpressionEvent', () => {
8+
it('should use correct region from projectConfig in event context', () => {
9+
const projectConfig = createProjectConfig(
10+
testData.getTestProjectConfig(),
11+
)
12+
13+
const experiment = projectConfig.experiments[0];
14+
const variation = experiment.variations[0];
15+
16+
const decisionObj = {
17+
experiment,
18+
variation,
19+
decisionSource: 'experiment',
20+
} as DecisionObj;
21+
22+
23+
const impressionEvent = buildImpressionEvent({
24+
configObj: projectConfig,
25+
decisionObj,
26+
userId: 'test_user',
27+
flagKey: 'test_flag',
28+
enabled: true,
29+
clientEngine: 'node-sdk',
30+
clientVersion: '1.0.0',
31+
});
32+
33+
expect(impressionEvent.context.region).toBe('US');
34+
35+
projectConfig.region = 'EU';
36+
37+
const impressionEventEU = buildImpressionEvent({
38+
configObj: projectConfig,
39+
decisionObj,
40+
userId: 'test_user',
41+
flagKey: 'test_flag',
42+
enabled: true,
43+
clientEngine: 'node-sdk',
44+
clientVersion: '1.0.0',
45+
});
46+
47+
expect(impressionEventEU.context.region).toBe('EU');
48+
});
49+
});
50+
51+
describe('buildConversionEvent', () => {
52+
it('should use correct region from projectConfig in event context', () => {
53+
const projectConfig = createProjectConfig(
54+
testData.getTestProjectConfig(),
55+
)
56+
57+
const conversionEvent = buildConversionEvent({
58+
configObj: projectConfig,
59+
userId: 'test_user',
60+
eventKey: 'test_event',
61+
eventTags: { revenue: 1000 },
62+
clientEngine: 'node-sdk',
63+
clientVersion: '1.0.0',
64+
});
65+
66+
expect(conversionEvent.context.region).toBe('US');
67+
68+
projectConfig.region = 'EU';
69+
70+
const conversionEventEU = buildConversionEvent({
71+
configObj: projectConfig,
72+
userId: 'test_user',
73+
eventKey: 'test_event',
74+
eventTags: { revenue: 1000 },
75+
clientEngine: 'node-sdk',
76+
clientVersion: '1.0.0',
77+
});
78+
79+
expect(conversionEventEU.context.region).toBe('EU');
80+
});
81+
});

lib/event_processor/event_builder/user_event.tests.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ describe('user_event', function() {
2626

2727
beforeEach(function() {
2828
configObj = {
29+
region: 'US',
2930
accountId: 'accountId',
3031
projectId: 'projectId',
3132
revision: '69',
@@ -106,6 +107,7 @@ describe('user_event', function() {
106107
timestamp: 100,
107108
uuid: 'uuid',
108109
context: {
110+
region: 'US',
109111
accountId: 'accountId',
110112
projectId: 'projectId',
111113
revision: '69',
@@ -200,6 +202,7 @@ describe('user_event', function() {
200202
timestamp: 100,
201203
uuid: 'uuid',
202204
context: {
205+
region: 'US',
203206
accountId: 'accountId',
204207
projectId: 'projectId',
205208
revision: '69',
@@ -270,6 +273,7 @@ describe('user_event', function() {
270273
timestamp: 100,
271274
uuid: 'uuid',
272275
context: {
276+
region: 'US',
273277
accountId: 'accountId',
274278
projectId: 'projectId',
275279
revision: '69',
@@ -336,6 +340,7 @@ describe('user_event', function() {
336340
timestamp: 100,
337341
uuid: 'uuid',
338342
context: {
343+
region: 'US',
339344
accountId: 'accountId',
340345
projectId: 'projectId',
341346
revision: '69',

lib/event_processor/event_builder/user_event.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
getEventId,
2424
getLayerId,
2525
ProjectConfig,
26+
Region,
2627
} from '../../project_config/project_config';
2728

2829
import { EventTags, UserAttributes } from '../../shared_types';
@@ -35,6 +36,7 @@ export type VisitorAttribute = {
3536
}
3637

3738
type EventContext = {
39+
region?: Region;
3840
accountId: string;
3941
projectId: string;
4042
revision: string;
@@ -97,7 +99,12 @@ export type UserEvent = ImpressionEvent | ConversionEvent;
9799
export const areEventContextsEqual = (eventA: UserEvent, eventB: UserEvent): boolean => {
98100
const contextA = eventA.context
99101
const contextB = eventB.context
102+
103+
const regionA: Region = contextA.region || 'US';
104+
const regionB: Region = contextB.region || 'US';
105+
100106
return (
107+
regionA === regionB &&
101108
contextA.accountId === contextB.accountId &&
102109
contextA.projectId === contextB.projectId &&
103110
contextA.clientName === contextB.clientName &&
@@ -156,6 +163,7 @@ export const buildImpressionEvent = function({
156163
},
157164

158165
context: {
166+
region: configObj.region,
159167
accountId: configObj.accountId,
160168
projectId: configObj.projectId,
161169
revision: configObj.revision,
@@ -228,6 +236,7 @@ export const buildConversionEvent = function({
228236
},
229237

230238
context: {
239+
region: configObj.region,
231240
accountId: configObj.accountId,
232241
projectId: configObj.projectId,
233242
revision: configObj.revision,

lib/project_config/project_config.spec.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import { describe, it, expect, beforeEach, afterEach, vi, assert, Mock } from 'vitest';
1717
import { sprintf } from '../utils/fns';
1818
import { keyBy } from '../utils/fns';
19-
import projectConfig, { ProjectConfig } from './project_config';
19+
import projectConfig, { ProjectConfig, Region } from './project_config';
2020
import { FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums';
2121
import testDatafile from '../tests/test_data';
2222
import configValidator from '../utils/config_validator';
@@ -40,6 +40,27 @@ const logger = getMockLogger();
4040
describe('createProjectConfig', () => {
4141
let configObj: ProjectConfig;
4242

43+
it('should use US region when no region is specified in datafile', () => {
44+
const datafile = testDatafile.getTestProjectConfig();
45+
const config = projectConfig.createProjectConfig(datafile);
46+
47+
expect(config.region).toBe('US');
48+
});
49+
50+
it('should parse region specified in datafile correctly', () => {
51+
const datafileUs = testDatafile.getTestProjectConfig();
52+
datafileUs.region = 'US';
53+
54+
const configUs = projectConfig.createProjectConfig(datafileUs);
55+
expect(configUs.region).toBe('US');
56+
57+
const datafileEu = testDatafile.getTestProjectConfig();
58+
datafileEu.region = 'EU';
59+
const configEu = projectConfig.createProjectConfig(datafileEu);
60+
61+
expect(configEu.region).toBe('EU');
62+
});
63+
4364
it('should set properties correctly when createProjectConfig is called', () => {
4465
const testData: Record<string, any> = testDatafile.getTestProjectConfig();
4566
configObj = projectConfig.createProjectConfig(testData as JSON);

lib/project_config/project_config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ interface VariableUsageMap {
7070
[id: string]: VariationVariable;
7171
}
7272

73+
export type Region = 'US' | 'EU';
74+
7375
export interface ProjectConfig {
76+
region: Region;
7477
revision: string;
7578
projectId: string;
7679
sdkKey: string;
@@ -155,6 +158,10 @@ function createMutationSafeDatafileCopy(datafile: any): ProjectConfig {
155158
export const createProjectConfig = function(datafileObj?: JSON, datafileStr: string | null = null): ProjectConfig {
156159
const projectConfig = createMutationSafeDatafileCopy(datafileObj);
157160

161+
if (!projectConfig.region) {
162+
projectConfig.region = 'US'; // Default to US region if not specified
163+
}
164+
158165
projectConfig.__datafileStr = datafileStr === null ? JSON.stringify(datafileObj) : datafileStr;
159166

160167
/*

0 commit comments

Comments
 (0)