Skip to content

Commit 74c739c

Browse files
Merge pull request #448 from elbwalker/447-data-policy
447 data policy
2 parents a596fbf + 152b2e3 commit 74c739c

File tree

17 files changed

+284
-91
lines changed

17 files changed

+284
-91
lines changed

.changeset/soft-moles-rush.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@elbwalker/destination-web-piwikpro': minor
3+
'@elbwalker/walker.js': minor
4+
'@elbwalker/client-node': minor
5+
'@elbwalker/types': minor
6+
'@elbwalker/utils': minor
7+
---
8+
9+
Data Policy [#447]https://(github.com/elbwalker/walkerOS/issues/447)

packages/clients/node/src/__tests__/destination.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,4 +345,46 @@ describe('Destination', () => {
345345
}),
346346
);
347347
});
348+
349+
test('policy', async () => {
350+
const event = createEvent();
351+
352+
const policy = {
353+
event: {
354+
value: 'new name',
355+
},
356+
'data.string': { value: 'bar' },
357+
'nested.0.type': { value: 'kid' },
358+
'data.number': {
359+
consent: { marketing: true },
360+
},
361+
'data.new': { value: 'value' },
362+
// timing: { value: 'now' }, // @TODO shouldn't be possible
363+
};
364+
365+
const destination: NodeDestination.Destination = {
366+
config: { policy },
367+
push: async (e) => {
368+
mockPush(e);
369+
},
370+
};
371+
372+
const { elb } = getClient({
373+
destinations: { destination },
374+
});
375+
376+
await elb(event);
377+
378+
expect(mockPush).toHaveBeenCalledWith({
379+
...event,
380+
event: 'new name',
381+
data: expect.objectContaining({
382+
string: 'bar',
383+
number: undefined, // Redacted due to missing consent
384+
new: 'value',
385+
}),
386+
nested: [expect.objectContaining({ type: 'kid' })],
387+
// timing: 0, // @TODO should be set to default type
388+
});
389+
});
348390
});

packages/clients/node/src/lib/push.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import type { NodeClient, NodeDestination } from '../types';
33
import {
44
assign,
55
getGrantedConsent,
6+
getMappingValue,
67
isSameType,
8+
setByPath,
79
tryCatchAsync,
810
} from '@elbwalker/utils';
911
import { isCommand } from './helper';
@@ -93,7 +95,16 @@ export async function pushToDestinations(
9395
destination.queue = []; // Reset original queue while processing
9496

9597
// Add event to queue stack
96-
if (event) queue.push(event);
98+
if (event) {
99+
// Policy check
100+
Object.entries(destination.config.policy || []).forEach(
101+
([key, mapping]) => {
102+
setByPath(event, key, getMappingValue(event, mapping, instance));
103+
},
104+
);
105+
106+
queue.push(event);
107+
}
97108

98109
// Nothing to do here if the queue is empty
99110
if (!queue.length) return { id, destination, skipped: true };

packages/clients/walkerjs/src/__tests__/destination.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { mockDataLayer } from '@elbwalker/jest/web.setup';
22
import type { WebClient, WebDestination } from '..';
33
import { elb, Walkerjs } from '..';
4+
import { createEvent } from '@elbwalker/utils';
45

56
describe('Destination', () => {
67
let walkerjs: WebClient.Instance;
@@ -844,4 +845,49 @@ describe('Destination', () => {
844845
}),
845846
);
846847
});
848+
849+
test('policy', () => {
850+
const event = createEvent();
851+
852+
const policy = {
853+
event: {
854+
value: 'new name',
855+
},
856+
'data.string': { value: 'bar' },
857+
'nested.0.type': { value: 'kid' },
858+
'data.number': {
859+
consent: { marketing: true },
860+
},
861+
'data.new': { value: 'value' },
862+
// timing: { value: 'now' }, // @TODO shouldn't be possible
863+
};
864+
865+
const destination: WebDestination.Destination = {
866+
config: { policy },
867+
push: (e) => {
868+
mockPush(e);
869+
},
870+
};
871+
872+
walkerjs = Walkerjs({
873+
run: true,
874+
pageview: false,
875+
session: false,
876+
destinations: { destination },
877+
});
878+
879+
elb(event);
880+
881+
expect(mockPush).toHaveBeenCalledWith({
882+
...event,
883+
event: 'new name',
884+
data: expect.objectContaining({
885+
string: 'bar',
886+
number: undefined, // Redacted due to missing consent
887+
new: 'value',
888+
}),
889+
nested: [expect.objectContaining({ type: 'kid' })],
890+
// timing: 0, // @TODO should be set to default type
891+
});
892+
});
847893
});

packages/clients/walkerjs/src/lib/push.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {
66
Const,
77
assign,
88
getGrantedConsent,
9+
getMappingValue,
910
isSameType,
11+
setByPath,
1012
tryCatch,
1113
useHooks,
1214
} from '@elbwalker/utils';
@@ -114,8 +116,17 @@ export function pushToDestinations(
114116
Object.values(destinations).forEach((destination) => {
115117
destination.queue = destination.queue || [];
116118

117-
// Add event to queue stack
118-
if (event) destination.queue.push(event);
119+
if (event) {
120+
// Policy check
121+
Object.entries(destination.config.policy || []).forEach(
122+
([key, mapping]) => {
123+
setByPath(event, key, getMappingValue(event, mapping, instance));
124+
},
125+
);
126+
127+
// Add event to queue stack
128+
destination.queue.push(event);
129+
}
119130

120131
const allowedEvents: WalkerOS.Events = [];
121132
destination.queue = destination.queue.filter((queuedEvent) => {

packages/destinations/web/piwikpro/src/index.test.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ describe('Destination PiwikPro', () => {
77

88
const mockFn = jest.fn(); //.mockImplementation(console.log);
99

10-
const event = 'entity action';
1110
const appId = 'XXX-XXX-XXX-XXX-XXX';
1211
const url = 'https://your_account_name.piwik.pro/';
1312

@@ -32,9 +31,9 @@ describe('Destination PiwikPro', () => {
3231
expect(true).toBeTruthy();
3332
});
3433

35-
test.skip('push', () => {
36-
elb('walker destination', destination);
37-
elb(event);
38-
// expect(mockFn).toHaveBeenNthCalledWith(1, event);
39-
});
34+
// test('push', () => {
35+
// elb('walker destination', destination);
36+
// elb(event);
37+
// // expect(mockFn).toHaveBeenNthCalledWith(1, event);
38+
// });
4039
});

packages/destinations/web/piwikpro/src/index.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Custom, CustomEvent, Destination } from './types';
2-
import { getByStringDot } from '@elbwalker/utils';
2+
import { getMappingValue } from '@elbwalker/utils';
33

44
// @TODOs
55
// - static values besides dynamic data values
@@ -51,7 +51,10 @@ export const destinationPiwikPro: Destination = {
5151
event.action === 'view'
5252
) {
5353
// Pageview tracking will move to run part in next version
54-
window._paq!.push(['trackPageView', getByStringDot(event, 'data.title')]);
54+
window._paq!.push([
55+
'trackPageView',
56+
getMappingValue(event, 'data.title'),
57+
]);
5558

5659
return;
5760
}
@@ -61,9 +64,9 @@ export const destinationPiwikPro: Destination = {
6164
let name: unknown, value: unknown; // @TODO fix types
6265

6366
if (customMapping) {
64-
if (customMapping.name) name = getByStringDot(event, customMapping.name);
67+
if (customMapping.name) name = getMappingValue(event, customMapping.name);
6568
if (customMapping.value)
66-
value = getByStringDot(event, customMapping.value);
69+
value = getMappingValue(event, customMapping.value);
6770
}
6871

6972
window._paq!.push([
@@ -77,7 +80,7 @@ export const destinationPiwikPro: Destination = {
7780

7881
if (customMapping.goalId) {
7982
const goalValue = customMapping.goalValue
80-
? getByStringDot(event, customMapping.goalValue)
83+
? getMappingValue(event, customMapping.goalValue)
8184
: undefined;
8285

8386
window._paq!.push([

packages/types/src/destination.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,17 @@ export interface Config<Custom = unknown, CustomEvent = unknown> {
1414
init?: boolean; // If the destination has been initialized by calling the init method
1515
loadScript?: boolean; // If an additional script to work should be loaded
1616
mapping?: Mapping.Config<CustomEvent>; // A map to handle events individually
17+
policy?: Policy; // Rules for processing events
1718
queue?: boolean; // Disable processing of previously pushed events
1819
verbose?: boolean; // Enable verbose logging
1920
onError?: Handler.Error; // Custom error handler
2021
onLog?: Handler.Log; // Custom log handler
2122
}
2223

24+
export interface Policy {
25+
[key: string]: Mapping.Value;
26+
}
27+
2328
export type PushEvent<CustomEvent = unknown> = {
2429
event: WalkerOS.Event;
2530
mapping?: Mapping.Event<CustomEvent>;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { createEvent, getByPath, setByPath } from '../core';
2+
3+
describe('byPath', () => {
4+
test('getByPath', () => {
5+
const obj = {
6+
foo: 'bar',
7+
a: { b: 'c' },
8+
i: [0, 1, { id: 'dynamic' }],
9+
};
10+
expect(getByPath(obj, 'foo')).toBe('bar');
11+
expect(getByPath(obj, 'unknown')).toBe(undefined);
12+
expect(getByPath(obj, 'a.b')).toBe('c');
13+
expect(getByPath(obj, 'i.2.id')).toBe('dynamic');
14+
expect(getByPath(obj, 'i.*.id', 'unknown', 2)).toStrictEqual([
15+
'unknown',
16+
'unknown',
17+
'dynamic',
18+
]);
19+
expect(getByPath(undefined, 'na')).toBe(undefined);
20+
});
21+
22+
test('setByPath', () => {
23+
const event = createEvent();
24+
25+
// expect(setByPath(event, 'foo', 'bar')).not.toHaveProperty('foo'); // @TODO
26+
expect(setByPath(event, 'timing', 2)).toHaveProperty('timing', 2);
27+
expect(setByPath(event, 'data.string', 'updated')).toHaveProperty(
28+
'data.string',
29+
'updated',
30+
);
31+
expect(setByPath(event, 'data.array.1', 'bar')).toHaveProperty(
32+
'data.array.1',
33+
'bar',
34+
);
35+
expect(setByPath(event, 'nested', [])).toHaveProperty('nested', []);
36+
expect(setByPath(event, 'data', { a: 1 })).toStrictEqual(
37+
expect.objectContaining({ data: { a: 1 } }),
38+
);
39+
});
40+
});

packages/utils/src/__tests__/getByStringDot.test.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)