Skip to content

Commit a7b62d9

Browse files
authored
[FSSDK-11207] add multi region support for logx events (#1072)
1 parent 7885261 commit a7b62d9

File tree

7 files changed

+198
-3
lines changed

7 files changed

+198
-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
@@ -17,9 +17,12 @@ import { describe, it, expect } from 'vitest';
1717

1818
import {
1919
makeEventBatch,
20+
buildLogEvent,
2021
} from './log_event';
2122

22-
import { ImpressionEvent, ConversionEvent } from './user_event';
23+
import { ImpressionEvent, ConversionEvent, UserEvent } from './user_event';
24+
import { Region } from '../../project_config/project_config';
25+
2326

2427
describe('makeEventBatch', () => {
2528
it('should build a batch with single impression event when experiment and variation are defined', () => {
@@ -804,3 +807,64 @@ describe('makeEventBatch', () => {
804807
})
805808
})
806809

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

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
@@ -215,8 +221,11 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor {
215221
}
216222

217223
export function buildLogEvent(events: UserEvent[]): LogEvent {
224+
const region = events[0]?.context.region || 'US';
225+
const url = logxEndpoint[region];
226+
218227
return {
219-
url: 'https://logx.optimizely.com/v1/events',
228+
url,
220229
httpVerb: 'POST',
221230
params: makeEventBatch(events),
222231
}
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: 8 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;
@@ -96,7 +98,12 @@ export type UserEvent = ImpressionEvent | ConversionEvent;
9698
export const areEventContextsEqual = (eventA: UserEvent, eventB: UserEvent): boolean => {
9799
const contextA = eventA.context
98100
const contextB = eventB.context
101+
102+
const regionA: Region = contextA.region || 'US';
103+
const regionB: Region = contextB.region || 'US';
104+
99105
return (
106+
regionA === regionB &&
100107
contextA.accountId === contextB.accountId &&
101108
contextA.projectId === contextB.projectId &&
102109
contextA.clientName === contextB.clientName &&
@@ -127,6 +134,7 @@ const buildBaseEvent = <T extends EventType>({
127134
timestamp: fns.currentTimestamp(),
128135
uuid: fns.uuid(),
129136
context: {
137+
region: configObj.region,
130138
accountId: configObj.accountId,
131139
projectId: configObj.projectId,
132140
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)