Skip to content

Commit

Permalink
Merge pull request #392 from elbwalker/367-bundle-event-pushes-to-des…
Browse files Browse the repository at this point in the history
…tinations

367 bundle event pushes to destinations
  • Loading branch information
alexanderkirtzel committed Jun 6, 2024
2 parents 89c4694 + 34e61d4 commit a6a4030
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 19 deletions.
8 changes: 8 additions & 0 deletions .changeset/strong-spoons-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@elbwalker/walker.js': major
'@elbwalker/types': major
'@elbwalker/utils': major
---

Bundle event pushes to destinations
(#367)[https://github.com/elbwalker/walkerOS/issues/367]
54 changes: 54 additions & 0 deletions packages/clients/walkerjs/src/__tests__/destination.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -652,4 +652,58 @@ describe('Destination', () => {

expect(mockPush).toHaveBeenCalledTimes(1);
});

test('batch', () => {
jest.useFakeTimers();
const mockBatch = jest.fn();

elb('walker destination', {
push: mockPush,
pushBatch: mockBatch,
config: {
mapping: {
'*': {
visible: { batch: 2000 },
click: { batch: 2000 },
important: {},
},
},
},
});
elb('walker run');

elb('foo visible');
elb('bar visible');
elb('foo click');
elb('foo important');

expect(mockPush).toHaveBeenCalledTimes(1); // Push important immediately
expect(mockBatch).toHaveBeenCalledTimes(0);
jest.runAllTimers();
expect(mockBatch).toHaveBeenCalledTimes(1);
expect(mockBatch).toHaveBeenNthCalledWith(
1,
[
{
event: expect.objectContaining({
event: 'foo visible',
}),
mapping: expect.objectContaining({ batch: 2000 }),
},
{
event: expect.objectContaining({
event: 'bar visible',
}),
mapping: expect.objectContaining({ batch: 2000 }),
},
expect.objectContaining({
event: expect.objectContaining({
event: 'foo click',
}),
}),
],
expect.anything(),
expect.anything(),
);
});
});
9 changes: 2 additions & 7 deletions packages/clients/walkerjs/src/lib/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,20 +20,15 @@ export function addDataLayerDestination(instance: WebClient.Instance) {

export function addDestination(
instance: WebClient.Instance,
data: Partial<WebDestination.DestinationInit>,
data: WebDestination.DestinationInit,
options?: WebDestination.Config,
) {
// Basic validation
if (!data.push) return;

// Prefer explicit given config over default config
const config = options || data.config || { init: false };

const destination: WebDestination.Destination = {
init: data.init,
push: data.push,
...data,
config,
type: data.type,
};

// Process previous events if not disabled
Expand Down
47 changes: 36 additions & 11 deletions packages/clients/walkerjs/src/lib/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import type { WalkerOS } from '@elbwalker/types';
import type { WebClient, WebDestination } from '../types';
import { allowedToPush, createEventOrCommand, isArgument } from './helper';
import { handleCommand, handleEvent } from './handle';
import { Const, isSameType, tryCatch, useHooks } from '@elbwalker/utils';
import {
Const,
assign,
debounce,
isSameType,
tryCatch,
useHooks,
} from '@elbwalker/utils';

export function createPush(instance: WebClient.Instance): WebClient.Elb {
const push = (
Expand Down Expand Up @@ -101,9 +108,8 @@ export function pushToDestination(
event: WalkerOS.Event,
useQueue = true,
): boolean {
// Deep copy event to prevent a pointer mess
// Update to structuredClone if support > 95%
event = JSON.parse(JSON.stringify(event));
// Copy the event to prevent mutation
event = assign({}, event);

// Always check for required consent states before pushing
if (!allowedToPush(instance, destination)) {
Expand Down Expand Up @@ -153,13 +159,32 @@ export function pushToDestination(
if (!init) return false;
}

// It's time to go to the destination's side now
useHooks(destination.push, 'DestinationPush', instance.hooks)(
event,
destination.config,
mappingEvent,
instance,
);
// Debounce the event if needed
const batch = mappingEvent?.batch;
if (batch && destination.pushBatch) {
destination.batch = destination.batch || [];
destination.batch.push({ event, mapping: mappingEvent });

destination.batchFn =
destination.batchFn ||
debounce((destination, instance) => {
useHooks(destination.pushBatch!, 'DestinationPush', instance.hooks)(
destination.batch || [],
destination.config,
instance,
);
}, batch);

destination.batchFn!(destination, instance);
} else {
// It's time to go to the destination's side now
useHooks(destination.push, 'DestinationPush', instance.hooks)(
event,
destination.config,
mappingEvent,
instance,
);
}

return true;
})();
Expand Down
8 changes: 8 additions & 0 deletions packages/clients/walkerjs/src/types/destination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ export interface Destination<Custom = never, EventCustom = never>
extends WalkerOSDestination.Destination<Custom, EventCustom> {
init?: (config: Config<Custom, EventCustom>) => void | boolean;
push: Push<Custom, EventCustom>;
pushBatch?: PushBatch<Custom, EventCustom>;
batchFn?: (destination: Destination, instance: WalkerOS.Instance) => void;
}

export type DestinationInit = Partial<Omit<Destination, 'push'>> &
Expand All @@ -19,6 +21,12 @@ export type Push<Custom, EventCustom> = (
instance?: WalkerOS.Instance,
) => void;

export type PushBatch<Custom, EventCustom> = (
events: WalkerOSDestination.Batch<EventCustom>,
config: Config<Custom, EventCustom>,
instance?: WalkerOS.Instance,
) => void;

export interface Config<Custom = never, EventCustom = never>
extends WalkerOSDestination.Config<Custom, EventCustom> {}

Expand Down
9 changes: 8 additions & 1 deletion packages/types/src/destination.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Handler, On, WalkerOS } from '.';

export interface Destination<Custom = unknown, EventCustom = unknown> {
config: Config<Custom, EventCustom>;
config: Config<Custom, EventCustom>; // Configuration settings for the destination
batch?: Batch<EventCustom>; // Batch of events to be processed
queue?: Queue; // Non processed events yet and reset with each new run
type?: string; // The type of the destination
}
Expand All @@ -25,12 +26,18 @@ export interface Mapping<EventCustom> {
[entity: string]: { [action: string]: EventConfig<EventCustom> };
}

export type Batch<EventCustom> = Array<{
event: WalkerOS.Event;
mapping?: EventConfig<EventCustom>;
}>;

export type Meta = {
name: string;
version: string;
};

export interface EventConfig<EventCustom = unknown> {
batch?: number; // Bundle events for batch processing
consent?: WalkerOS.Consent; // Required consent states to init and push events
custom?: EventCustom; // Arbitrary but protected configurations for custom event config
ignore?: boolean; // Choose to no process an event when set to true
Expand Down

0 comments on commit a6a4030

Please sign in to comment.