Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Context from '../../../src/Context';
import EventFactoryBase from '../../../src/internal/evaluation/EventFactoryBase';

const context = Context.fromLDContext({ key: 'user-key' });

function evalEvent(factory: EventFactoryBase, overrideAffected?: boolean) {
return factory.evalEvent({
context,
defaultVal: 'default',
flagKey: 'flag',
trackEvents: true,
value: 'value',
variation: 1,
version: 3,
reason: { kind: 'FALLTHROUGH' },
overrideAffected,
});
}

it('carries the override marking on the evaluation event', () => {
const event = evalEvent(new EventFactoryBase(true), true);
expect(event.overrideAffected).toBe(true);
expect(event.key).toEqual('flag');
expect(event.trackEvents).toBe(true);
});

it('omits the marking when the evaluation was not affected', () => {
expect(evalEvent(new EventFactoryBase(true), false).overrideAffected).toBe(false);
expect(evalEvent(new EventFactoryBase(true))).not.toHaveProperty('overrideAffected');
});

it('does not mark an unknown flag event', () => {
const event = new EventFactoryBase(false).unknownFlagEvent('flag', 'default', context);
expect(event).not.toHaveProperty('overrideAffected');
});
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,54 @@ describe('given an event processor', () => {
]);
});

it('produces no feature or debug event for an override-affected evaluation and marks its counter', async () => {
Date.now = jest.fn(() => 1000);
// Both flags request individual feature events and debug events. Only the override-affected
// evaluation is kept out of the individual event stream.
const base = {
kind: 'feature' as const,
creationDate: 1000,
context: Context.fromLDContext(user),
version: 11,
variation: 1,
value: 'value',
trackEvents: true,
debugEventsUntilDate: 2000,
default: 'default',
samplingRatio: 1,
withReasons: true,
};
eventProcessor.sendEvent({ ...base, key: 'flagkey', overrideAffected: true });
eventProcessor.sendEvent({ ...base, key: 'plain' });

await eventProcessor.flush();

expect(mockSendEventData).toBeCalledWith(LDEventType.AnalyticsEvents, [
testIndexEvent,
makeFeatureEvent(1000, 11, false, 'plain'),
makeFeatureEvent(1000, 11, true, 'plain'),
{
endDate: 1000,
features: {
flagkey: {
contextKinds: ['user'],
counters: [
{ count: 1, value: 'value', variation: 1, version: 11, overrideAffected: true },
],
default: 'default',
},
plain: {
contextKinds: ['user'],
counters: [{ count: 1, value: 'value', variation: 1, version: 11 }],
default: 'default',
},
},
kind: 'summary',
startDate: 1000,
},
]);
});

it('uses sampling ratio for feature events', async () => {
Date.now = jest.fn(() => 1000);
eventProcessor.sendEvent({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,3 +318,38 @@ describe('given an event summarizer', () => {
expect(secondSummary.endDate).toBe(0);
});
});

it('keeps override-affected evaluations in a separate counter that carries the marker', () => {
const summarizer = new EventSummarizer();
const context = Context.fromLDContext({ key: 'key' });
const base = {
kind: 'feature',
creationDate: 1000,
key: 'key1',
version: 11,
context,
variation: 1,
value: 100,
default: 111,
};

summarizer.summarizeEvent(base as any);
summarizer.summarizeEvent({ ...base, overrideAffected: true } as any);
summarizer.summarizeEvent({ ...base, overrideAffected: true } as any);

const data = summarizer.getSummary();
const { counters } = data.features.key1;
expect(counters).toHaveLength(2);

const plain = counters.find((counter) => !counter.overrideAffected);
const marked = counters.find((counter) => counter.overrideAffected);
expect(plain).toEqual({ value: 100, count: 1, variation: 1, version: 11 });
expect(plain).not.toHaveProperty('overrideAffected');
expect(marked).toEqual({
value: 100,
count: 2,
variation: 1,
version: 11,
overrideAffected: true,
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type EvalEventArgs = {
defaultVal: any;
excludeFromSummaries?: boolean;
flagKey: string;
overrideAffected?: boolean;
prereqOfFlagKey?: string;
reason?: LDEvaluationReason;
samplingRatio?: number;
Expand Down Expand Up @@ -37,6 +38,7 @@ export default class EventFactoryBase {
e.debugEventsUntilDate,
e.excludeFromSummaries,
e.samplingRatio,
e.overrideAffected,
);
}

Expand Down
9 changes: 7 additions & 2 deletions packages/shared/common/src/internal/events/EventProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,14 @@ export default class EventProcessor implements LDEventProcessor {

const isFeatureEvent = isFeature(inputEvent);

const addFullEvent = (isFeatureEvent && inputEvent.trackEvents) || !isFeatureEvent;
// An override-affected evaluation appears only in the summary counters. It produces no
// individual feature event and no debug event, even when the flag requests them.
const overrideAffected = isFeatureEvent && !!inputEvent.overrideAffected;

const addDebugEvent = this._shouldDebugEvent(inputEvent);
const addFullEvent =
(isFeatureEvent && inputEvent.trackEvents && !overrideAffected) || !isFeatureEvent;

const addDebugEvent = !overrideAffected && this._shouldDebugEvent(inputEvent);
Comment thread
kinyoklion marked this conversation as resolved.
Comment thread
kinyoklion marked this conversation as resolved.

const isIdentifyEvent = isIdentify(inputEvent);
const shouldNotDeduplicate = this._contextDeduplicator?.processContext(inputEvent.context);
Expand Down
11 changes: 10 additions & 1 deletion packages/shared/common/src/internal/events/EventSummarizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ import LDEventSummarizer, {
} from './LDEventSummarizer';
import SummaryCounter from './SummaryCounter';

// The override marker is part of the key, so override-affected and other evaluations of the same
// flag, variation, and version accumulate into separate counters.
function counterKey(event: InputEvalEvent) {
return `${event.key}:${
event.variation !== null && event.variation !== undefined ? event.variation : ''
}:${event.version !== null && event.version !== undefined ? event.version : ''}`;
}:${event.version !== null && event.version !== undefined ? event.version : ''}:${
event.overrideAffected ? 'overrideAffected' : ''
}`;
}

/**
Expand Down Expand Up @@ -59,6 +63,7 @@ export default class EventSummarizer implements LDEventSummarizer {
event.default,
event.version,
event.variation,
event.overrideAffected,
);
}

Expand Down Expand Up @@ -96,6 +101,10 @@ export default class EventSummarizer implements LDEventSummarizer {
} else {
counterOut.unknown = true;
}
if (counter.overrideAffected) {
// The marker is present only when true, like the unknown marker.
counterOut.overrideAffected = true;
}
flagSummary.counters.push(counterOut);

return acc;
Expand Down
12 changes: 12 additions & 0 deletions packages/shared/common/src/internal/events/InputEvalEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ export default class InputEvalEvent {

public readonly excludeFromSummaries?: boolean;

/**
* True when an override affected the evaluation, directly or transitively. The SDK sets it
* from the evaluation result, not from the reason. A marked evaluation produces no individual
* feature or debug event and is counted in a summary counter that carries the marker.
*/
public readonly overrideAffected?: boolean;

constructor(
public readonly withReasons: boolean,
public readonly context: Context,
Expand All @@ -38,6 +45,7 @@ export default class InputEvalEvent {
debugEventsUntilDate?: number,
excludeFromSummaries?: boolean,
public readonly samplingRatio: number = 1,
overrideAffected?: boolean,
) {
this.creationDate = Date.now();
this.value = value;
Expand Down Expand Up @@ -70,5 +78,9 @@ export default class InputEvalEvent {
if (excludeFromSummaries !== undefined) {
this.excludeFromSummaries = excludeFromSummaries;
}

if (overrideAffected !== undefined) {
this.overrideAffected = overrideAffected;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export interface FlagCounter {
variation?: number;
version?: number;
unknown?: boolean;
/**
* Present, and true, only for a counter that aggregates override-affected evaluations.
*/
overrideAffected?: boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default class SummaryCounter {
defValue: any,
public version?: number,
public variation?: number,
public overrideAffected?: boolean,
) {
this.default = defValue;
}
Expand Down
Loading
Loading