Skip to content

Commit

Permalink
chore: implement variation functions (#298)
Browse files Browse the repository at this point in the history
  • Loading branch information
yusinto authored Oct 13, 2023
1 parent 8f34dfa commit fc2c212
Show file tree
Hide file tree
Showing 21 changed files with 407 additions and 134 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/**
* Different kinds of error which may be encountered during evaluation.
*
* @internal
*/
enum ErrorKinds {
MalformedFlag = 'MALFORMED_FLAG',
Expand All @@ -11,7 +9,4 @@ enum ErrorKinds {
WrongType = 'WRONG_TYPE',
}

/**
* @internal
*/
export default ErrorKinds;
85 changes: 85 additions & 0 deletions packages/shared/common/src/internal/evaluation/EventFactoryBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { LDEvaluationReason, LDFlagValue } from '../../api';
import Context from '../../Context';
import { InputCustomEvent, InputEvalEvent, InputIdentifyEvent } from '../events';

export type EvalEventArgs = {
addExperimentData?: boolean;
context: Context;
debugEventsUntilDate?: number;
defaultVal: any;
excludeFromSummaries?: boolean;
flagKey: string;
prereqOfFlagKey?: string;
reason?: LDEvaluationReason;
samplingRatio?: number;
trackEvents: boolean;
value: LDFlagValue;
variation?: number;
version: number;
};

export default class EventFactoryBase {
constructor(private readonly withReasons: boolean) {}

evalEvent(e: EvalEventArgs): InputEvalEvent {
return new InputEvalEvent(
this.withReasons,
e.context,
e.flagKey,
e.value,
e.defaultVal,
e.version,
// Exclude null as a possibility.
e.variation ?? undefined,
e.trackEvents || e.addExperimentData,
e.prereqOfFlagKey,
this.withReasons || e.addExperimentData ? e.reason : undefined,
e.debugEventsUntilDate,
e.excludeFromSummaries,
e.samplingRatio,
);
}

unknownFlagEvent(key: string, defVal: LDFlagValue, context: Context) {
return new InputEvalEvent(
this.withReasons,
context,
key,
defVal,
defVal,
// This isn't ideal, but the purpose of the factory is to at least
// handle this situation.
undefined, // version
undefined, // variation index
undefined, // track events
undefined, // prereqOf
undefined, // reason
undefined, // debugEventsUntilDate
undefined, // exclude from summaries
undefined, // sampling ratio
);
}

/* eslint-disable-next-line class-methods-use-this */
identifyEvent(context: Context) {
// Currently sampling for identify events is always 1.
return new InputIdentifyEvent(context, 1);
}

/* eslint-disable-next-line class-methods-use-this */
customEvent(
key: string,
context: Context,
data?: any,
metricValue?: number,
samplingRatio: number = 1,
) {
return new InputCustomEvent(
context,
key,
data ?? undefined,
metricValue ?? undefined,
samplingRatio,
);
}
}
18 changes: 18 additions & 0 deletions packages/shared/common/src/internal/evaluation/evaluationDetail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { LDEvaluationReason, LDFlagValue } from '../../api';
import ErrorKinds from './ErrorKinds';

export const createErrorEvaluationDetail = (errorKind: ErrorKinds, def?: LDFlagValue) => ({
value: def ?? null,
variationIndex: null,
reason: { kind: 'ERROR', errorKind },
});

export const createSuccessEvaluationDetail = (
value: LDFlagValue,
variationIndex?: number,
reason?: LDEvaluationReason,
) => ({
value,
variationIndex: variationIndex ?? null,
reason: reason ?? null,
});
11 changes: 11 additions & 0 deletions packages/shared/common/src/internal/evaluation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import ErrorKinds from './ErrorKinds';
import { createErrorEvaluationDetail, createSuccessEvaluationDetail } from './evaluationDetail';
import EventFactoryBase, { EvalEventArgs } from './EventFactoryBase';

export {
createSuccessEvaluationDetail,
createErrorEvaluationDetail,
ErrorKinds,
EvalEventArgs,
EventFactoryBase,
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
/**
* Messages for issues which can be encountered processing client requests.
*
* @internal
*/
export default class ClientMessages {
static readonly missingContextKeyNoEvent =
Expand Down
7 changes: 3 additions & 4 deletions packages/shared/common/src/internal/events/InputEvalEvent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LDEvaluationDetail, LDEvaluationReason } from '../../api/data';
import { LDEvaluationReason, LDFlagValue } from '../../api/data';
import Context from '../../Context';

export default class InputEvalEvent {
Expand Down Expand Up @@ -28,8 +28,8 @@ export default class InputEvalEvent {
public readonly withReasons: boolean,
public readonly context: Context,
public readonly key: string,
value: LDFlagValue,
defValue: any, // default is a reserved keyword in this context.
detail: LDEvaluationDetail,
version?: number,
variation?: number,
trackEvents?: boolean,
Expand All @@ -40,9 +40,8 @@ export default class InputEvalEvent {
public readonly samplingRatio: number = 1,
) {
this.creationDate = Date.now();
this.value = value;
this.default = defValue;
this.variation = detail.variationIndex ?? undefined;
this.value = detail.value;

if (version !== undefined) {
this.version = version;
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/common/src/internal/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import ClientMessages from './ClientMessages';
import EventProcessor from './EventProcessor';
import InputCustomEvent from './InputCustomEvent';
import InputEvalEvent from './InputEvalEvent';
Expand All @@ -8,6 +9,7 @@ import NullEventProcessor from './NullEventProcessor';
import shouldSample from './sampling';

export {
ClientMessages,
InputCustomEvent,
InputEvalEvent,
InputEvent,
Expand Down
1 change: 1 addition & 0 deletions packages/shared/common/src/internal/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './diagnostics';
export * from './evaluation';
export * from './events';
export * from './stream';
3 changes: 3 additions & 0 deletions packages/shared/common/src/utils/clone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function clone(obj: any) {
return JSON.parse(JSON.stringify(obj));
}
12 changes: 11 additions & 1 deletion packages/shared/common/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import clone from './clone';
import { secondsToMillis } from './date';
import { defaultHeaders, httpErrorMessage, LDHeaders } from './http';
import noop from './noop';
import sleep from './sleep';
import { VoidFunction } from './VoidFunction';

export { defaultHeaders, httpErrorMessage, noop, LDHeaders, secondsToMillis, sleep, VoidFunction };
export {
clone,
defaultHeaders,
httpErrorMessage,
noop,
LDHeaders,
secondsToMillis,
sleep,
VoidFunction,
};
83 changes: 83 additions & 0 deletions packages/shared/sdk-client/src/LDClientImpl.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { LDContext } from '@launchdarkly/js-sdk-common';
import { basicPlatform } from '@launchdarkly/private-js-mocks';

import fetchFlags from './evaluation/fetchFlags';
import * as mockResponseJson from './evaluation/mockResponse.json';
import LDClientImpl from './LDClientImpl';

jest.mock('./evaluation/fetchFlags', () => {
const actual = jest.requireActual('./evaluation/fetchFlags');
return {
__esModule: true,
...actual,
default: jest.fn(),
};
});

describe('sdk-client object', () => {
const testSdkKey = 'test-sdk-key';
const context: LDContext = { kind: 'org', key: 'Testy Pizza' };
const mockFetchFlags = fetchFlags as jest.Mock;

let ldc: LDClientImpl;

beforeEach(async () => {
mockFetchFlags.mockResolvedValue(mockResponseJson);

ldc = new LDClientImpl(testSdkKey, context, basicPlatform, {});
await ldc.start();
});

test('instantiate with blank options', () => {
expect(ldc.config).toMatchObject({
allAttributesPrivate: false,
baseUri: 'https://sdk.launchdarkly.com',
capacity: 100,
diagnosticOptOut: false,
diagnosticRecordingInterval: 900,
eventsUri: 'https://events.launchdarkly.com',
flushInterval: 2,
inspectors: [],
logger: {
destination: expect.any(Function),
formatter: expect.any(Function),
logLevel: 1,
name: 'LaunchDarkly',
},
privateAttributes: [],
sendEvents: true,
sendLDHeaders: true,
serviceEndpoints: {
events: 'https://events.launchdarkly.com',
polling: 'https://sdk.launchdarkly.com',
streaming: 'https://clientstream.launchdarkly.com',
},
streamInitialReconnectDelay: 1,
streamUri: 'https://clientstream.launchdarkly.com',
tags: {},
useReport: false,
withReasons: false,
});
});

test('all flags', async () => {
const all = ldc.allFlags();

expect(all).toEqual({
'dev-test-flag': true,
'easter-i-tunes-special': false,
'easter-specials': 'no specials',
fdsafdsafdsafdsa: true,
'log-level': 'warn',
'moonshot-demo': true,
test1: 's1',
'this-is-a-test': true,
});
});

test('variation', async () => {
const devTestFlag = ldc.variation('dev-test-flag');

expect(devTestFlag).toBe(true);
});
});
Loading

0 comments on commit fc2c212

Please sign in to comment.