Skip to content

Commit

Permalink
Merge branch 'rlamb/new-data-kinds' of github.com:launchdarkly/js-ser…
Browse files Browse the repository at this point in the history
…ver-sdk-private into rlamb/new-data-kinds
  • Loading branch information
kinyoklion committed Aug 3, 2023
2 parents 87c22e8 + 2b9e022 commit 482a738
Show file tree
Hide file tree
Showing 20 changed files with 1,144 additions and 236 deletions.
5 changes: 1 addition & 4 deletions packages/shared/common/src/Context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ import { TypeValidators } from './validators';
// This is to reduce work on the hot-path. Later, for event processing, deeper
// cloning of the context will be done.

// Validates a kind excluding check that it isn't "kind".
const KindValidator = TypeValidators.stringMatchingRegex(/^(\w|\.|-)+$/);

// When no kind is specified, then this kind will be used.
const DEFAULT_KIND = 'user';

Expand Down Expand Up @@ -98,7 +95,7 @@ function isContextCommon(
* @returns true if the kind is valid.
*/
function validKind(kind: string) {
return KindValidator.is(kind) && kind !== 'kind';
return TypeValidators.Kind.is(kind);
}

/**
Expand Down
12 changes: 11 additions & 1 deletion packages/shared/common/src/internal/events/EventProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import AttributeReference from '../../AttributeReference';
import ContextFilter from '../../ContextFilter';
import ClientContext from '../../options/ClientContext';
import EventSummarizer, { SummarizedFlagsEvent } from './EventSummarizer';
import { isFeature, isIdentify } from './guards';
import { isFeature, isIdentify, isMigration } from './guards';
import InputEvent from './InputEvent';
import LDInvalidSDKKeyError from './LDInvalidSDKKeyError';

Expand Down Expand Up @@ -196,6 +196,16 @@ export default class EventProcessor implements LDEventProcessor {
return;
}

if (isMigration(inputEvent)) {
// The contents of the migration op event have been validated
// before this point, so we can just send it.
// TODO: Implement sampling odds.
this.enqueue({
...inputEvent,
});
return;
}

this.summarizer.summarizeEvent(inputEvent);

const isFeatureEvent = isFeature(inputEvent);
Expand Down
3 changes: 2 additions & 1 deletion packages/shared/common/src/internal/events/InputEvent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import InputCustomEvent from './InputCustomEvent';
import InputEvalEvent from './InputEvalEvent';
import InputIdentifyEvent from './InputIdentifyEvent';
import InputMigrationEvent from './InputMigrationEvent';

type InputEvent = InputEvalEvent | InputCustomEvent | InputIdentifyEvent;
type InputEvent = InputEvalEvent | InputCustomEvent | InputIdentifyEvent | InputMigrationEvent;
export default InputEvent;
13 changes: 13 additions & 0 deletions packages/shared/common/src/internal/events/InputMigrationEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Migration events are not currently supported by client-side SDKs, so this
// shared implementation contains minimal typing. If/When migration events are
// to be supported by client-side SDKs the appropriate types would be moved
// to the common implementation.

export default interface InputMigrationEvent {
kind: 'migration_op';
operation: string;
creationDate: number;
contextKeys: Record<string, string>;
evaluation: any;
measurements: any[];
}
5 changes: 5 additions & 0 deletions packages/shared/common/src/internal/events/guards.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import InputCustomEvent from './InputCustomEvent';
import InputEvalEvent from './InputEvalEvent';
import InputIdentifyEvent from './InputIdentifyEvent';
import InputMigrationEvent from './InputMigrationEvent';

export function isFeature(u: any): u is InputEvalEvent {
return u.kind === 'feature';
Expand All @@ -13,3 +14,7 @@ export function isCustom(u: any): u is InputCustomEvent {
export function isIdentify(u: any): u is InputIdentifyEvent {
return u.kind === 'identify';
}

export function isMigration(u: any): u is InputMigrationEvent {
return u.kind === 'migration_op';
}
10 changes: 9 additions & 1 deletion packages/shared/common/src/internal/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,13 @@ import InputCustomEvent from './InputCustomEvent';
import InputEvalEvent from './InputEvalEvent';
import InputEvent from './InputEvent';
import InputIdentifyEvent from './InputIdentifyEvent';
import InputMigrationEvent from './InputMigrationEvent';

export { InputCustomEvent, InputEvalEvent, InputEvent, InputIdentifyEvent, EventProcessor };
export {
InputCustomEvent,
InputEvalEvent,
InputEvent,
InputIdentifyEvent,
InputMigrationEvent,
EventProcessor,
};
15 changes: 15 additions & 0 deletions packages/shared/common/src/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,19 @@ export class DateValidator implements TypeValidator {
}
}

/**
* Validates that a string is a valid kind.
*/
export class KindValidator extends StringMatchingRegex {
constructor() {
super(/^(\w|\.|-)+$/);
}

override is(u: unknown): u is string {
return super.is(u) && u !== 'kind';
}
}

/**
* A set of standard type validators.
*/
Expand Down Expand Up @@ -188,4 +201,6 @@ export class TypeValidators {
}

static readonly Date = new DateValidator();

static readonly Kind = new KindValidator();
}
19 changes: 3 additions & 16 deletions packages/shared/sdk-server/__tests__/LDClient.migrations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('given an LDClient with test data', () => {
{ key: 'test-key' },
defaultValue as LDMigrationStage,
);
expect(res).toEqual(value);
expect(res.value).toEqual(value);
},
);

Expand All @@ -76,30 +76,17 @@ describe('given an LDClient with test data', () => {
])('returns the default value if the flag does not exist: default = %p', async (stage) => {
const res = await client.variationMigration('no-flag', { key: 'test-key' }, stage);

expect(res).toEqual(stage);
expect(res.value).toEqual(stage);
});

it('produces an error event for a migration flag with an incorrect value', async () => {
const flagKey = 'bad-migration';
td.update(td.flag(flagKey).valueForAll('potato'));
const res = await client.variationMigration(flagKey, { key: 'test-key' }, LDMigrationStage.Off);
expect(res).toEqual(LDMigrationStage.Off);
expect(res.value).toEqual(LDMigrationStage.Off);
expect(errors.length).toEqual(1);
expect(errors[0].message).toEqual(
'Unrecognized MigrationState for "bad-migration"; returning default value.',
);
});

it('includes an error in the node callback', (done) => {
const flagKey = 'bad-migration';
td.update(td.flag(flagKey).valueForAll('potato'));
client.variationMigration(flagKey, { key: 'test-key' }, LDMigrationStage.Off, (err, value) => {
const error = err as Error;
expect(error.message).toEqual(
'Unrecognized MigrationState for "bad-migration"; returning default value.',
);
expect(value).toEqual(LDMigrationStage.Off);
done();
});
});
});
Loading

0 comments on commit 482a738

Please sign in to comment.