From c7ae35aecdc26d74c0ecaef109f32c736759ff31 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Fri, 20 Sep 2024 15:20:08 -0400 Subject: [PATCH] feat: implement tracking as per spec Signed-off-by: Todd Baert --- packages/angular/angular.json | 3 ++- packages/server/src/client/client.ts | 2 ++ .../client/internal/open-feature-client.ts | 5 +++++ packages/server/src/index.ts | 1 + packages/server/src/provider/provider.ts | 18 ++++++++++++++++- packages/server/src/tracking/index.ts | 1 + packages/server/src/tracking/tracking.ts | 12 +++++++++++ packages/shared/src/evaluation/context.ts | 2 +- packages/shared/src/evaluation/evaluation.ts | 11 ++-------- packages/shared/src/index.ts | 1 + packages/shared/src/tracking/index.ts | 1 + packages/shared/src/tracking/occurrence.ts | 20 +++++++++++++++++++ packages/shared/src/types/index.ts | 3 ++- packages/shared/src/types/structure.ts | 7 +++++++ packages/web/src/client/client.ts | 8 +++++++- .../client/internal/open-feature-client.ts | 9 +++++++++ packages/web/src/index.ts | 1 + packages/web/src/provider/provider.ts | 10 +++++++++- packages/web/src/tracking/index.ts | 1 + packages/web/src/tracking/tracking.ts | 11 ++++++++++ 20 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 packages/server/src/tracking/index.ts create mode 100644 packages/server/src/tracking/tracking.ts create mode 100644 packages/shared/src/tracking/index.ts create mode 100644 packages/shared/src/tracking/occurrence.ts create mode 100644 packages/shared/src/types/structure.ts create mode 100644 packages/web/src/tracking/index.ts create mode 100644 packages/web/src/tracking/tracking.ts diff --git a/packages/angular/angular.json b/packages/angular/angular.json index 3fdc6e91d..1d9cc72d7 100644 --- a/packages/angular/angular.json +++ b/packages/angular/angular.json @@ -39,6 +39,7 @@ "cli": { "schematicCollections": [ "@angular-eslint/schematics" - ] + ], + "analytics": false } } diff --git a/packages/server/src/client/client.ts b/packages/server/src/client/client.ts index 6c31fa6eb..485646943 100644 --- a/packages/server/src/client/client.ts +++ b/packages/server/src/client/client.ts @@ -8,12 +8,14 @@ import { import { Features } from '../evaluation'; import { ProviderStatus } from '../provider'; import { ProviderEvents } from '../events'; +import { Tracking } from '../tracking'; export interface Client extends EvaluationLifeCycle, Features, ManageContext, ManageLogger, + Tracking, Eventing { readonly metadata: ClientMetadata; /** diff --git a/packages/server/src/client/internal/open-feature-client.ts b/packages/server/src/client/internal/open-feature-client.ts index 0e3cd1bbc..efb69e459 100644 --- a/packages/server/src/client/internal/open-feature-client.ts +++ b/packages/server/src/client/internal/open-feature-client.ts @@ -9,6 +9,7 @@ import { HookContext, JsonValue, Logger, + OccurrenceDetails, OpenFeatureError, ProviderFatalError, ProviderNotReadyError, @@ -221,6 +222,10 @@ export class OpenFeatureClient implements Client { return this.evaluate(flagKey, this._provider.resolveObjectEvaluation, defaultValue, 'object', context, options); } + track(occurrenceKey: string, context: EvaluationContext, occurrenceDetails: OccurrenceDetails): void { + return this._provider.track?.(occurrenceKey, context, occurrenceDetails); + } + private async evaluate( flagKey: string, resolver: ( diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index c229d16fb..249135cbf 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -5,4 +5,5 @@ export * from './open-feature'; export * from './transaction-context'; export * from './events'; export * from './hooks'; +export * from './tracking'; export * from '@openfeature/core'; diff --git a/packages/server/src/provider/provider.ts b/packages/server/src/provider/provider.ts index 63173d3d0..a8e90a246 100644 --- a/packages/server/src/provider/provider.ts +++ b/packages/server/src/provider/provider.ts @@ -1,4 +1,12 @@ -import { CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails, ServerProviderStatus } from '@openfeature/core'; +import { + CommonProvider, + EvaluationContext, + JsonValue, + Logger, + OccurrenceDetails, + ResolutionDetails, + ServerProviderStatus, +} from '@openfeature/core'; import { Hook } from '../hooks'; export { ServerProviderStatus as ProviderStatus }; @@ -57,4 +65,12 @@ export interface Provider extends CommonProvider { context: EvaluationContext, logger: Logger, ): Promise>; + + /** + * Track a thing + * @param occurrenceKey + * @param context + * @param occurrenceDetails + */ + track?(occurrenceKey: string, context: EvaluationContext, occurrenceDetails: OccurrenceDetails): void; } diff --git a/packages/server/src/tracking/index.ts b/packages/server/src/tracking/index.ts new file mode 100644 index 000000000..5794a1d40 --- /dev/null +++ b/packages/server/src/tracking/index.ts @@ -0,0 +1 @@ +export * from './tracking'; diff --git a/packages/server/src/tracking/tracking.ts b/packages/server/src/tracking/tracking.ts new file mode 100644 index 000000000..9ae6f9597 --- /dev/null +++ b/packages/server/src/tracking/tracking.ts @@ -0,0 +1,12 @@ +import { EvaluationContext, OccurrenceDetails } from '@openfeature/core'; + +export interface Tracking { + + /** + * Track a thing + * @param occurrenceKey + * @param context + * @param occurrenceDetails + */ + track(occurrenceKey: string, context: EvaluationContext, occurrenceDetails: OccurrenceDetails): void; +} diff --git a/packages/shared/src/evaluation/context.ts b/packages/shared/src/evaluation/context.ts index 14de9dbfd..4cde4b078 100644 --- a/packages/shared/src/evaluation/context.ts +++ b/packages/shared/src/evaluation/context.ts @@ -1,4 +1,4 @@ -import { PrimitiveValue } from './evaluation'; +import { PrimitiveValue } from '../types'; export type EvaluationContextValue = | PrimitiveValue diff --git a/packages/shared/src/evaluation/evaluation.ts b/packages/shared/src/evaluation/evaluation.ts index c900b40f2..dfb0b02a7 100644 --- a/packages/shared/src/evaluation/evaluation.ts +++ b/packages/shared/src/evaluation/evaluation.ts @@ -1,13 +1,6 @@ -export type FlagValueType = 'boolean' | 'string' | 'number' | 'object'; - -export type PrimitiveValue = null | boolean | string | number; -export type JsonObject = { [key: string]: JsonValue }; -export type JsonArray = JsonValue[]; +import { JsonValue } from '../types/structure'; -/** - * Represents a JSON node value. - */ -export type JsonValue = PrimitiveValue | JsonObject | JsonArray; +export type FlagValueType = 'boolean' | 'string' | 'number' | 'object'; /** * Represents a JSON node value, or Date. diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 8a85f4142..42c1504a0 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -7,4 +7,5 @@ export * from './logger'; export * from './provider'; export * from './evaluation'; export * from './type-guards'; +export * from './tracking'; export * from './open-feature'; diff --git a/packages/shared/src/tracking/index.ts b/packages/shared/src/tracking/index.ts new file mode 100644 index 000000000..47af9db54 --- /dev/null +++ b/packages/shared/src/tracking/index.ts @@ -0,0 +1 @@ +export * from './occurrence'; diff --git a/packages/shared/src/tracking/occurrence.ts b/packages/shared/src/tracking/occurrence.ts new file mode 100644 index 000000000..db7a73ca5 --- /dev/null +++ b/packages/shared/src/tracking/occurrence.ts @@ -0,0 +1,20 @@ +import { PrimitiveValue } from '../types'; + + +export type OccurrenceValue = + | PrimitiveValue + | Date + | { [key: string]: OccurrenceValue } + | OccurrenceValue[]; + +/** + * A container for arbitrary contextual data that can be used as a basis for dynamic evaluation + */ +export type OccurrenceDetails = { + /** + * A string uniquely identifying the subject (end-user, or client service) of a flag evaluation. + * Providers may require this field for fractional flag evaluation, rules, or overrides targeting specific users. + * Such providers may behave unpredictably if a targeting key is not specified at flag resolution. + */ + value?: number; +} & Record; diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 69cf2f63f..6c96c078a 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -1,2 +1,3 @@ export * from './metadata'; -export * from './paradigm'; \ No newline at end of file +export * from './paradigm'; +export * from './structure'; diff --git a/packages/shared/src/types/structure.ts b/packages/shared/src/types/structure.ts new file mode 100644 index 000000000..7edbdd937 --- /dev/null +++ b/packages/shared/src/types/structure.ts @@ -0,0 +1,7 @@ +export type PrimitiveValue = null | boolean | string | number; +export type JsonObject = { [key: string]: JsonValue }; +export type JsonArray = JsonValue[]; +/** + * Represents a JSON node value. + */ +export type JsonValue = PrimitiveValue | JsonObject | JsonArray; diff --git a/packages/web/src/client/client.ts b/packages/web/src/client/client.ts index 699359e68..9f8b8502f 100644 --- a/packages/web/src/client/client.ts +++ b/packages/web/src/client/client.ts @@ -2,8 +2,14 @@ import { ClientMetadata, EvaluationLifeCycle, Eventing, ManageLogger } from '@op import { Features } from '../evaluation'; import { ProviderStatus } from '../provider'; import { ProviderEvents } from '../events'; +import { Tracking } from '../tracking'; -export interface Client extends EvaluationLifeCycle, Features, ManageLogger, Eventing { +export interface Client + extends EvaluationLifeCycle, + Features, + ManageLogger, + Eventing, + Tracking { readonly metadata: ClientMetadata; /** * Returns the status of the associated provider. diff --git a/packages/web/src/client/internal/open-feature-client.ts b/packages/web/src/client/internal/open-feature-client.ts index 7e649e39d..b2c863842 100644 --- a/packages/web/src/client/internal/open-feature-client.ts +++ b/packages/web/src/client/internal/open-feature-client.ts @@ -9,6 +9,7 @@ import { HookContext, JsonValue, Logger, + OccurrenceDetails, OpenFeatureError, ProviderFatalError, ProviderNotReadyError, @@ -179,6 +180,14 @@ export class OpenFeatureClient implements Client { return this.evaluate(flagKey, this._provider.resolveObjectEvaluation, defaultValue, 'object', options); } + track(occurrenceKey: string, occurrenceDetails: OccurrenceDetails): void { + const context = { + ...OpenFeature.getContext(this?.options?.domain), + }; + + return this._provider.track?.(occurrenceKey, context, occurrenceDetails); + } + private evaluate( flagKey: string, resolver: (flagKey: string, defaultValue: T, context: EvaluationContext, logger: Logger) => ResolutionDetails, diff --git a/packages/web/src/index.ts b/packages/web/src/index.ts index 355cd9ce1..fa481b733 100644 --- a/packages/web/src/index.ts +++ b/packages/web/src/index.ts @@ -4,4 +4,5 @@ export * from './evaluation'; export * from './open-feature'; export * from './events'; export * from './hooks'; +export * from './tracking'; export * from '@openfeature/core'; diff --git a/packages/web/src/provider/provider.ts b/packages/web/src/provider/provider.ts index 6b51ec73f..c3b24182f 100644 --- a/packages/web/src/provider/provider.ts +++ b/packages/web/src/provider/provider.ts @@ -1,4 +1,4 @@ -import { ClientProviderStatus, CommonProvider, EvaluationContext, JsonValue, Logger, ResolutionDetails } from '@openfeature/core'; +import { ClientProviderStatus, CommonProvider, EvaluationContext, JsonValue, Logger, OccurrenceDetails, ResolutionDetails } from '@openfeature/core'; import { Hook } from '../hooks'; export { ClientProviderStatus as ProviderStatus }; @@ -71,4 +71,12 @@ export interface Provider extends CommonProvider { context: EvaluationContext, logger: Logger, ): ResolutionDetails; + + /** + * Track a thing + * @param occurrenceKey + * @param context + * @param occurrenceDetails + */ + track?(occurrenceKey: string, context: EvaluationContext, occurrenceDetails: OccurrenceDetails): void; } diff --git a/packages/web/src/tracking/index.ts b/packages/web/src/tracking/index.ts new file mode 100644 index 000000000..5794a1d40 --- /dev/null +++ b/packages/web/src/tracking/index.ts @@ -0,0 +1 @@ +export * from './tracking'; diff --git a/packages/web/src/tracking/tracking.ts b/packages/web/src/tracking/tracking.ts new file mode 100644 index 000000000..f6137f474 --- /dev/null +++ b/packages/web/src/tracking/tracking.ts @@ -0,0 +1,11 @@ +import { OccurrenceDetails } from '@openfeature/core'; + +export interface Tracking { + + /** + * Track a thing + * @param occurrenceKey + * @param occurrenceDetails + */ + track(occurrenceKey: string, occurrenceDetails: OccurrenceDetails): void; +}