Skip to content

Commit b162da0

Browse files
authored
Add support for named global context (#1331)
The `addGlobalContexts` API now accepts an `object` mapping "names" to context primitives or context generators, rather than just an array of "unnamed" global entities/generators. Calling `addGlobalContexts` multiple times with values of the same "name" will overwrite the previous value for subsequent events, allowing "upserting" or updating of global context without having to remove the previous version first and introducing duplicates. `removeGlobalContexts` can now also accept plain strings which are handled as names, so primitives/generators can be removed by name without having to own a reference to the generating function or an object that serializes to the same string.
1 parent b43e9d8 commit b162da0

File tree

7 files changed

+154
-24
lines changed

7 files changed

+154
-24
lines changed

api-docs/docs/browser-tracker/browser-tracker.api.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface ActivityTrackingConfigurationCallback {
2929
}
3030

3131
// @public
32-
export function addGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive>, trackers?: Array<string>): void;
32+
export function addGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive> | Record<string, ConditionalContextProvider | ContextPrimitive>, trackers?: Array<string>): void;
3333

3434
// @public
3535
export function addPlugin(configuration: BrowserPluginConfiguration, trackers?: Array<string>): void;
@@ -293,7 +293,7 @@ export function preservePageViewId(trackers?: Array<string>): void;
293293
export type PreservePageViewIdForUrl = boolean | "full" | "pathname" | "pathnameAndSearch";
294294

295295
// @public
296-
export function removeGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive>, trackers?: Array<string>): void;
296+
export function removeGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive | string>, trackers?: Array<string>): void;
297297

298298
// @public
299299
export type RequestFailure = {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@snowplow/browser-tracker",
5+
"comment": "Add support for named global context",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@snowplow/browser-tracker"
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@snowplow/tracker-core",
5+
"comment": "Add support for named global context",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@snowplow/tracker-core"
10+
}

libraries/tracker-core/src/contexts.ts

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,11 @@ export interface GlobalContexts {
116116
* Adds conditional or primitive global contexts
117117
* @param contexts - An Array of either Conditional Contexts or Primitive Contexts
118118
*/
119-
addGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive>): void;
119+
addGlobalContexts(
120+
contexts:
121+
| Array<ConditionalContextProvider | ContextPrimitive>
122+
| Record<string, ConditionalContextProvider | ContextPrimitive>
123+
): void;
120124

121125
/**
122126
* Removes all global contexts
@@ -127,7 +131,7 @@ export interface GlobalContexts {
127131
* Removes previously added global context, performs a deep comparison of the contexts or conditional contexts
128132
* @param contexts - An Array of either Condition Contexts or Primitive Contexts
129133
*/
130-
removeGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive>): void;
134+
removeGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive | string>): void;
131135

132136
/**
133137
* Returns all applicable global contexts for a specified event
@@ -142,6 +146,8 @@ export interface GlobalContexts {
142146
export function globalContexts(): GlobalContexts {
143147
let globalPrimitives: Array<ContextPrimitive> = [];
144148
let conditionalProviders: Array<ConditionalContextProvider> = [];
149+
let namedPrimitives: Record<string, ContextPrimitive> = {};
150+
let namedConditionalProviders: Record<string, ConditionalContextProvider> = {};
145151

146152
/**
147153
* Returns all applicable global contexts for a specified event
@@ -152,46 +158,75 @@ export function globalContexts(): GlobalContexts {
152158
const eventSchema = getUsefulSchema(event);
153159
const eventType = getEventType(event);
154160
const contexts: Array<SelfDescribingJson> = [];
155-
const generatedPrimitives = generatePrimitives(globalPrimitives, event, eventType, eventSchema);
161+
const generatedPrimitives = generatePrimitives(
162+
globalPrimitives.concat(Object.values(namedPrimitives)),
163+
event,
164+
eventType,
165+
eventSchema
166+
);
156167
contexts.push(...generatedPrimitives);
157168

158-
const generatedConditionals = generateConditionals(conditionalProviders, event, eventType, eventSchema);
169+
const generatedConditionals = generateConditionals(
170+
conditionalProviders.concat(Object.values(namedConditionalProviders)),
171+
event,
172+
eventType,
173+
eventSchema
174+
);
159175
contexts.push(...generatedConditionals);
160176

161177
return contexts;
162178
};
163179

164180
return {
165181
getGlobalPrimitives(): Array<ContextPrimitive> {
166-
return globalPrimitives;
182+
return globalPrimitives.concat(Object.values(namedPrimitives));
167183
},
168184

169185
getConditionalProviders(): Array<ConditionalContextProvider> {
170-
return conditionalProviders;
186+
return conditionalProviders.concat(Object.values(namedConditionalProviders));
171187
},
172188

173-
addGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive>): void {
174-
const acceptedConditionalContexts: ConditionalContextProvider[] = [];
175-
const acceptedContextPrimitives: ContextPrimitive[] = [];
176-
for (const context of contexts) {
177-
if (isConditionalContextProvider(context)) {
178-
acceptedConditionalContexts.push(context);
179-
} else if (isContextPrimitive(context)) {
180-
acceptedContextPrimitives.push(context);
189+
addGlobalContexts(
190+
contexts:
191+
| Array<ConditionalContextProvider | ContextPrimitive>
192+
| Record<string, ConditionalContextProvider | ContextPrimitive>
193+
): void {
194+
if (Array.isArray(contexts)) {
195+
const acceptedConditionalContexts: ConditionalContextProvider[] = [];
196+
const acceptedContextPrimitives: ContextPrimitive[] = [];
197+
for (const context of contexts) {
198+
if (isConditionalContextProvider(context)) {
199+
acceptedConditionalContexts.push(context);
200+
} else if (isContextPrimitive(context)) {
201+
acceptedContextPrimitives.push(context);
202+
}
203+
}
204+
globalPrimitives = globalPrimitives.concat(acceptedContextPrimitives);
205+
conditionalProviders = conditionalProviders.concat(acceptedConditionalContexts);
206+
} else {
207+
for (const [name, context] of Object.entries(contexts)) {
208+
if (isConditionalContextProvider(context)) {
209+
namedConditionalProviders[name] = context;
210+
} else if (isContextPrimitive(context)) {
211+
namedPrimitives[name] = context;
212+
}
181213
}
182214
}
183-
globalPrimitives = globalPrimitives.concat(acceptedContextPrimitives);
184-
conditionalProviders = conditionalProviders.concat(acceptedConditionalContexts);
185215
},
186216

187217
clearGlobalContexts(): void {
188218
conditionalProviders = [];
189219
globalPrimitives = [];
220+
namedConditionalProviders = {};
221+
namedPrimitives = {};
190222
},
191223

192-
removeGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive>): void {
224+
removeGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive | string>): void {
193225
for (const context of contexts) {
194-
if (isConditionalContextProvider(context)) {
226+
if (typeof context === 'string') {
227+
delete namedConditionalProviders[context];
228+
delete namedPrimitives[context];
229+
} else if (isConditionalContextProvider(context)) {
195230
conditionalProviders = conditionalProviders.filter(
196231
(item) => JSON.stringify(item) !== JSON.stringify(context)
197232
);

libraries/tracker-core/src/core.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,11 @@ export interface TrackerCore {
273273
* Adds contexts globally, contexts added here will be attached to all applicable events
274274
* @param contexts - An array containing either contexts or a conditional contexts
275275
*/
276-
addGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive>): void;
276+
addGlobalContexts(
277+
contexts:
278+
| Array<ConditionalContextProvider | ContextPrimitive>
279+
| Record<string, ConditionalContextProvider | ContextPrimitive>
280+
): void;
277281

278282
/**
279283
* Removes all global contexts
@@ -284,7 +288,7 @@ export interface TrackerCore {
284288
* Removes previously added global context, performs a deep comparison of the contexts or conditional contexts
285289
* @param contexts - An array containing either contexts or a conditional contexts
286290
*/
287-
removeGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive>): void;
291+
removeGlobalContexts(contexts: Array<ConditionalContextProvider | ContextPrimitive | string>): void;
288292

289293
/**
290294
* Add a plugin into the plugin collection after Core has already been initialised

libraries/tracker-core/test/contexts.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,75 @@ test('Add global contexts', (t) => {
215215
t.is(globalContexts.getConditionalProviders().length, 2, 'Correct number of conditional providers added');
216216
});
217217

218+
test('Handle named global contexts', (t) => {
219+
const geolocationContext = {
220+
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',
221+
data: {
222+
latitude: 40.0,
223+
longitude: 55.1,
224+
},
225+
};
226+
227+
function eventTypeContextGenerator(args?: contexts.ContextEvent) {
228+
const context: SelfDescribingJson = {
229+
schema: 'iglu:com.snowplowanalytics.snowplow/mobile_context/jsonschema/1-0-1',
230+
data: {
231+
osType: 'ubuntu',
232+
osVersion: '2018.04',
233+
deviceManufacturer: 'ASUS',
234+
deviceModel: args ? String(args['eventType']) : '',
235+
},
236+
};
237+
return context;
238+
}
239+
240+
const bothRuleSet = {
241+
accept: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*'],
242+
reject: ['iglu:com.snowplowanalytics.snowplow/*/jsonschema/*-*-*'],
243+
};
244+
245+
const filterFunction = function (args?: contexts.ContextEvent) {
246+
return args?.eventType === 'ue';
247+
};
248+
249+
const filterProvider: contexts.FilterProvider = [filterFunction, [geolocationContext, eventTypeContextGenerator]];
250+
const ruleSetProvider: contexts.RuleSetProvider = [bothRuleSet, [geolocationContext, eventTypeContextGenerator]];
251+
252+
const namedContexts = {
253+
filters: filterProvider,
254+
rules: ruleSetProvider,
255+
static: geolocationContext,
256+
generator: eventTypeContextGenerator,
257+
};
258+
const globalContexts = contexts.globalContexts();
259+
260+
globalContexts.addGlobalContexts(namedContexts);
261+
t.is(globalContexts.getGlobalPrimitives().length, 2, 'Correct number of primitives added');
262+
t.is(globalContexts.getConditionalProviders().length, 2, 'Correct number of conditional providers added');
263+
264+
globalContexts.removeGlobalContexts(['static', 'rules']);
265+
t.is(globalContexts.getGlobalPrimitives().length, 1, 'Correct number of primitives removed');
266+
t.is(globalContexts.getConditionalProviders().length, 1, 'Correct number of conditional providers removed');
267+
268+
globalContexts.clearGlobalContexts();
269+
t.is(globalContexts.getGlobalPrimitives().length, 0, 'All primitives removed');
270+
t.is(globalContexts.getConditionalProviders().length, 0, 'All conditional providers removed');
271+
272+
globalContexts.addGlobalContexts([geolocationContext]);
273+
globalContexts.addGlobalContexts({ geo: geolocationContext });
274+
t.is(globalContexts.getGlobalPrimitives().length, 2, 'Treats anonymous and named globals separately');
275+
276+
const mutatedGeolocationContext = JSON.parse(JSON.stringify(geolocationContext));
277+
mutatedGeolocationContext.data.latitude = 30;
278+
279+
globalContexts.addGlobalContexts({ geo: mutatedGeolocationContext });
280+
t.deepEqual(
281+
globalContexts.getGlobalPrimitives(),
282+
[geolocationContext, mutatedGeolocationContext],
283+
'Upserts named globals'
284+
);
285+
});
286+
218287
test('Remove one of two global context primitives', (t) => {
219288
const geolocationContext = {
220289
schema: 'iglu:com.snowplowanalytics.snowplow/geolocation_context/jsonschema/1-1-0',

trackers/browser-tracker/src/api.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,9 @@ export function trackSelfDescribingEvent(event: SelfDescribingEvent & CommonEven
411411
* @param trackers - The tracker identifiers which the global contexts will be added to
412412
*/
413413
export function addGlobalContexts(
414-
contexts: Array<ConditionalContextProvider | ContextPrimitive>,
414+
contexts:
415+
| Array<ConditionalContextProvider | ContextPrimitive>
416+
| Record<string, ConditionalContextProvider | ContextPrimitive>,
415417
trackers?: Array<string>
416418
) {
417419
dispatchToTrackers(trackers, (t) => {
@@ -426,7 +428,7 @@ export function addGlobalContexts(
426428
* @param trackers - The tracker identifiers which the global contexts will be remove from
427429
*/
428430
export function removeGlobalContexts(
429-
contexts: Array<ConditionalContextProvider | ContextPrimitive>,
431+
contexts: Array<ConditionalContextProvider | ContextPrimitive | string>,
430432
trackers?: Array<string>
431433
) {
432434
dispatchToTrackers(trackers, (t) => {

0 commit comments

Comments
 (0)