Skip to content

Commit b0478ec

Browse files
authored
feat: Added a Lite bundle excluding Data file manager and Event Processor packages. (#699)
## Summary Some platforms (such as edge providers), do not need extended functionality offered by data file manager and event processor. This PR creates a lite bundle which uses a `NoOpDatafileManager` and a `ForwardingEventProcessor` which immediately dispatches events when a custom event dispatcher is provided. This results in up to 20% reduction in bundle size. This also fixes the errors that SDK was throwing on edge platforms. ### Usage To use the lite bundle, user needs to explicitly import from it. `import { createInstance } from '@optimizely/optimizely-sdk/dist/optimizely.lite.es.mins.js'` ## Test plan 1. All the Full stack compatibility suite tests pass for the existing bundles. 2. Fixed all the failing unit tests. 3. Added new unit tests to `lite` entry point.
1 parent 7a73b91 commit b0478ec

25 files changed

+1254
-215
lines changed

packages/optimizely-sdk/lib/core/decision_service/index.tests.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
DECISION_SOURCES,
2525
} from '../../utils/enums';
2626
import { createLogger } from '../../plugins/logger';
27+
import { createForwardingEventProcessor } from '../../plugins/event_processor/forwarding_event_processor';
28+
import { createNotificationCenter } from '../notification_center';
2729
import Optimizely from '../../optimizely';
2830
import projectConfig from '../project_config';
2931
import AudienceEvaluator from '../audience_evaluator';
@@ -980,7 +982,8 @@ describe('lib/core/decision_service', function() {
980982
jsonSchemaValidator: jsonSchemaValidator,
981983
isValidInstance: true,
982984
logger: createdLogger,
983-
eventDispatcher: eventDispatcher,
985+
eventProcessor: createForwardingEventProcessor(eventDispatcher),
986+
notificationCenter: createNotificationCenter(createdLogger, errorHandler),
984987
errorHandler: errorHandler,
985988
});
986989

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
/**
2+
* Copyright 2021 Optimizely
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import {
17+
EventTags,
18+
ConversionEvent,
19+
ImpressionEvent,
20+
} from '@optimizely/js-sdk-event-processor';
21+
22+
import { Event } from '../../shared_types';
23+
24+
type ProcessableEvent = ConversionEvent | ImpressionEvent
25+
26+
const ACTIVATE_EVENT_KEY = 'campaign_activated'
27+
const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
28+
const BOT_FILTERING_KEY = '$opt_bot_filtering'
29+
30+
export type EventV1 = {
31+
account_id: string
32+
project_id: string
33+
revision: string
34+
client_name: string
35+
client_version: string
36+
anonymize_ip: boolean
37+
enrich_decisions: boolean
38+
visitors: Visitor[]
39+
}
40+
41+
type Visitor = {
42+
snapshots: Snapshot[]
43+
visitor_id: string
44+
attributes: Attribute[]
45+
}
46+
47+
type AttributeType = 'custom'
48+
49+
export type Attribute = {
50+
// attribute id
51+
entity_id: string
52+
// attribute key
53+
key: string
54+
type: AttributeType
55+
value: string | number | boolean
56+
}
57+
58+
export type Snapshot = {
59+
decisions?: Decision[]
60+
events: SnapshotEvent[]
61+
}
62+
63+
type Decision = {
64+
campaign_id: string | null
65+
experiment_id: string | null
66+
variation_id: string | null
67+
metadata: Metadata
68+
}
69+
70+
type Metadata = {
71+
flag_key: string;
72+
rule_key: string;
73+
rule_type: string;
74+
variation_key: string;
75+
enabled: boolean;
76+
}
77+
78+
export type SnapshotEvent = {
79+
entity_id: string | null
80+
timestamp: number
81+
uuid: string
82+
key: string
83+
revenue?: number
84+
value?: number
85+
tags?: EventTags
86+
}
87+
88+
/**
89+
* Given an array of batchable Decision or ConversionEvent events it returns
90+
* a single EventV1 with proper batching
91+
*
92+
* @param {ProcessableEvent[]} events
93+
* @returns {EventV1}
94+
*/
95+
export function makeBatchedEventV1(events: ProcessableEvent[]): EventV1 {
96+
const visitors: Visitor[] = []
97+
const data = events[0]
98+
99+
events.forEach(event => {
100+
if (event.type === 'conversion' || event.type === 'impression') {
101+
const visitor = makeVisitor(event)
102+
103+
if (event.type === 'impression') {
104+
visitor.snapshots.push(makeDecisionSnapshot(event))
105+
} else if (event.type === 'conversion') {
106+
visitor.snapshots.push(makeConversionSnapshot(event))
107+
}
108+
109+
visitors.push(visitor)
110+
}
111+
})
112+
113+
return {
114+
client_name: data.context.clientName,
115+
client_version: data.context.clientVersion,
116+
117+
account_id: data.context.accountId,
118+
project_id: data.context.projectId,
119+
revision: data.context.revision,
120+
anonymize_ip: data.context.anonymizeIP,
121+
enrich_decisions: true,
122+
123+
visitors,
124+
}
125+
}
126+
127+
function makeConversionSnapshot(conversion: ConversionEvent): Snapshot {
128+
const tags: EventTags = {
129+
...conversion.tags,
130+
}
131+
132+
delete tags['revenue']
133+
delete tags['value']
134+
135+
const event: SnapshotEvent = {
136+
entity_id: conversion.event.id,
137+
key: conversion.event.key,
138+
timestamp: conversion.timestamp,
139+
uuid: conversion.uuid,
140+
}
141+
142+
if (conversion.tags) {
143+
event.tags = conversion.tags
144+
}
145+
146+
if (conversion.value != null) {
147+
event.value = conversion.value
148+
}
149+
150+
if (conversion.revenue != null) {
151+
event.revenue = conversion.revenue
152+
}
153+
154+
return {
155+
events: [event],
156+
}
157+
}
158+
159+
function makeDecisionSnapshot(event: ImpressionEvent): Snapshot {
160+
const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event
161+
const layerId = layer ? layer.id : null
162+
const experimentId = experiment ? experiment.id : null
163+
const variationId = variation ? variation.id : null
164+
const variationKey = variation ? variation.key : ''
165+
166+
return {
167+
decisions: [
168+
{
169+
campaign_id: layerId,
170+
experiment_id: experimentId,
171+
variation_id: variationId,
172+
metadata: {
173+
flag_key: flagKey,
174+
rule_key: ruleKey,
175+
rule_type: ruleType,
176+
variation_key: variationKey,
177+
enabled: enabled,
178+
},
179+
},
180+
],
181+
events: [
182+
{
183+
entity_id: layerId,
184+
timestamp: event.timestamp,
185+
key: ACTIVATE_EVENT_KEY,
186+
uuid: event.uuid,
187+
},
188+
],
189+
}
190+
}
191+
192+
function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor {
193+
const visitor: Visitor = {
194+
snapshots: [],
195+
visitor_id: data.user.id,
196+
attributes: [],
197+
}
198+
199+
data.user.attributes.forEach(attr => {
200+
visitor.attributes.push({
201+
entity_id: attr.entityId,
202+
key: attr.key,
203+
type: 'custom' as const, // tell the compiler this is always string "custom"
204+
value: attr.value,
205+
})
206+
})
207+
208+
if (typeof data.context.botFiltering === 'boolean') {
209+
visitor.attributes.push({
210+
entity_id: BOT_FILTERING_KEY,
211+
key: BOT_FILTERING_KEY,
212+
type: CUSTOM_ATTRIBUTE_FEATURE_TYPE,
213+
value: data.context.botFiltering,
214+
})
215+
}
216+
return visitor
217+
}
218+
219+
/**
220+
* Event for usage with v1 logtier
221+
*
222+
* @export
223+
* @interface EventBuilderV1
224+
*/
225+
export function buildImpressionEventV1(data: ImpressionEvent): EventV1 {
226+
const visitor = makeVisitor(data)
227+
visitor.snapshots.push(makeDecisionSnapshot(data))
228+
229+
return {
230+
client_name: data.context.clientName,
231+
client_version: data.context.clientVersion,
232+
233+
account_id: data.context.accountId,
234+
project_id: data.context.projectId,
235+
revision: data.context.revision,
236+
anonymize_ip: data.context.anonymizeIP,
237+
enrich_decisions: true,
238+
239+
visitors: [visitor],
240+
}
241+
}
242+
243+
export function buildConversionEventV1(data: ConversionEvent): EventV1 {
244+
const visitor = makeVisitor(data)
245+
visitor.snapshots.push(makeConversionSnapshot(data))
246+
247+
return {
248+
client_name: data.context.clientName,
249+
client_version: data.context.clientVersion,
250+
251+
account_id: data.context.accountId,
252+
project_id: data.context.projectId,
253+
revision: data.context.revision,
254+
anonymize_ip: data.context.anonymizeIP,
255+
enrich_decisions: true,
256+
257+
visitors: [visitor],
258+
}
259+
}
260+
261+
export function formatEvents(events: ProcessableEvent[]): Event {
262+
return {
263+
url: 'https://logx.optimizely.com/v1/events',
264+
httpVerb: 'POST',
265+
params: makeBatchedEventV1(events),
266+
}
267+
}

0 commit comments

Comments
 (0)