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
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[submodule "tests/engine/engine-tests/engine-test-data"]
path = tests/engine/engine-tests/engine-test-data
url = [email protected]:Flagsmith/engine-test-data.git
branch = v2.5.0
branch = v3.1.0
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@
*/

/**
* An environment's unique identifier.
* Unique environment key. May be used for selecting a value for a multivariate feature, or for % split segmentation.
*/
export type Key = string;
/**
* An environment's human-readable name.
*/
export type Name = string;
/**
* A unique identifier for an identity, used for segment and multivariate feature flag targeting, and displayed in the Flagsmith UI.
* A unique identifier for an identity as displayed in the Flagsmith UI.
*/
export type Identifier = string;
/**
* Key used when selecting a value for a multivariate feature, or for % split segmentation. Set to an internal identifier or a composite value based on the environment key and identifier, depending on Flagsmith implementation.
*/
export type Key1 = string;
/**
* Key used for % split segmentation.
* Unique segment key used for % split segmentation.
*/
export type Key2 = string;
/**
Expand Down Expand Up @@ -85,13 +85,9 @@ export type SubRules = SegmentRule[];
*/
export type Rules = SegmentRule[];
/**
* Key used when selecting a value for a multivariate feature. Set to an internal identifier or a UUID, depending on Flagsmith implementation.
* Unique feature key used when selecting a variant if the feature is multivariate. Set to an internal identifier or a UUID, depending on Flagsmith implementation.
*/
export type Key3 = string;
/**
* Unique feature identifier.
*/
export type FeatureKey = string;
/**
* Feature name.
*/
Expand Down Expand Up @@ -155,7 +151,7 @@ export interface EnvironmentContext {
*/
export interface IdentityContext {
identifier: Identifier;
key: Key1;
key?: Key1;
traits?: Traits;
[k: string]: unknown;
}
Expand Down Expand Up @@ -214,7 +210,6 @@ export interface InSegmentCondition {
*/
export interface FeatureContext {
key: Key3;
feature_key: FeatureKey;
name: Name2;
enabled: Enabled;
value: Value2;
Expand Down
47 changes: 30 additions & 17 deletions flagsmith-engine/evaluation/evaluationContext/mappers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {
FeaturesWithMetadata,
Segments,
Traits,
GenericEvaluationContext,
EnvironmentContext,
IdentityContext,
SegmentSource
SegmentSource,
SDKFeatureMetadata,
SegmentsWithMetadata,
SDKSegmentMetadata
} from '../models.js';
import { EnvironmentModel } from '../../environments/models.js';
import { IdentityModel } from '../../identities/models.js';
Expand All @@ -17,9 +19,13 @@ import { uuidToBigInt } from '../../features/util.js';
export function getEvaluationContext(
environment: EnvironmentModel,
identity?: IdentityModel,
overrideTraits?: TraitModel[]
overrideTraits?: TraitModel[],
isEnvironmentEvaluation: boolean = false
): GenericEvaluationContext {
const environmentContext = mapEnvironmentModelToEvaluationContext(environment);
if (isEnvironmentEvaluation) {
return environmentContext;
}
const identityContext = identity
? mapIdentityModelToIdentityContext(identity, overrideTraits)
: undefined;
Expand All @@ -40,7 +46,7 @@ function mapEnvironmentModelToEvaluationContext(
name: environment.project.name
};

const features: FeaturesWithMetadata = {};
const features: FeaturesWithMetadata<SDKFeatureMetadata> = {};
for (const fs of environment.featureStates) {
const variants =
fs.multivariateFeatureStateValues?.length > 0
Expand All @@ -53,19 +59,18 @@ function mapEnvironmentModelToEvaluationContext(

features[fs.feature.name] = {
key: fs.djangoID?.toString() || fs.featurestateUUID,
feature_key: fs.feature.id.toString(),
name: fs.feature.name,
enabled: fs.enabled,
value: fs.getValue(),
variants,
priority: fs.featureSegment?.priority,
metadata: {
flagsmithId: fs.feature.id
id: fs.feature.id
}
};
}

const segmentOverrides: Segments = {};
const segmentOverrides: SegmentsWithMetadata<SDKSegmentMetadata> = {};
for (const segment of environment.project.segments) {
segmentOverrides[segment.id.toString()] = {
key: segment.id.toString(),
Expand All @@ -75,21 +80,23 @@ function mapEnvironmentModelToEvaluationContext(
segment.featureStates.length > 0
? segment.featureStates.map(fs => ({
key: fs.djangoID?.toString() || fs.featurestateUUID,
feature_key: fs.feature.id.toString(),
name: fs.feature.name,
enabled: fs.enabled,
value: fs.getValue(),
priority: fs.featureSegment?.priority
priority: fs.featureSegment?.priority,
metadata: {
id: fs.feature.id
}
}))
: [],
metadata: {
source: SegmentSource.API,
flagsmith_id: segment.id
id: segment.id
}
};
}

let identityOverrideSegments: Segments = {};
let identityOverrideSegments: SegmentsWithMetadata<SDKSegmentMetadata> = {};
if (environment.identityOverrides && environment.identityOverrides.length > 0) {
identityOverrideSegments = mapIdentityOverridesToSegments(environment.identityOverrides);
}
Expand All @@ -115,11 +122,16 @@ function mapIdentityModelToIdentityContext(
traitsContext[trait.traitKey] = trait.traitValue;
}

return {
const identityContext: IdentityContext = {
identifier: identity.identifier,
key: identity.djangoID?.toString() || identity.compositeKey,
traits: traitsContext
};

if (identity.djangoID !== undefined) {
identityContext.key = identity.djangoID.toString();
}

return identityContext;
}

function mapSegmentRuleModelToRule(rule: any): any {
Expand All @@ -134,8 +146,10 @@ function mapSegmentRuleModelToRule(rule: any): any {
};
}

function mapIdentityOverridesToSegments(identityOverrides: IdentityModel[]): Segments {
const segments: Segments = {};
function mapIdentityOverridesToSegments(
identityOverrides: IdentityModel[]
): SegmentsWithMetadata<SDKSegmentMetadata> {
const segments: SegmentsWithMetadata<SDKSegmentMetadata> = {};
const featuresToIdentifiers = new Map<string, { identifiers: string[]; overrides: any[] }>();

for (const identity of identityOverrides) {
Expand All @@ -147,13 +161,12 @@ function mapIdentityOverridesToSegments(identityOverrides: IdentityModel[]): Seg
a.feature.name.localeCompare(b.feature.name)
);
const overridesKey = sortedFeatures.map(fs => ({
feature_key: fs.feature.id.toString(),
name: fs.feature.name,
enabled: fs.enabled,
value: fs.getValue(),
priority: -Infinity,
metadata: {
flagsmithId: fs.feature.id
id: fs.feature.id
}
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@
* and run json-schema-to-typescript to regenerate this file.
*/

/**
* Unique feature identifier.
*/
export type FeatureKey = string;
/**
* Feature name.
*/
Expand All @@ -25,10 +21,6 @@ export type Value = string | number | boolean | null;
* Reason for the feature flag evaluation.
*/
export type Reason = string;
/**
* Unique segment identifier.
*/
export type Key = string;
/**
* Segment name.
*/
Expand All @@ -53,7 +45,6 @@ export interface Flags {
[k: string]: FlagResult;
}
export interface FlagResult {
feature_key: FeatureKey;
name: Name;
enabled: Enabled;
value: Value;
Expand All @@ -68,7 +59,6 @@ export interface FeatureMetadata {
[k: string]: unknown;
}
export interface SegmentResult {
key: Key;
name: Name1;
metadata?: SegmentMetadata;
[k: string]: unknown;
Expand Down
76 changes: 54 additions & 22 deletions flagsmith-engine/evaluation/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,27 @@
import type {
EvaluationResult as EvaluationContextResult,
FlagResult,
FeatureMetadata
FeatureMetadata,
SegmentMetadata
} from './evaluationResult/evaluationResult.types.js';

import type {
FeatureContext,
EnvironmentContext,
IdentityContext,
Segments
SegmentContext
} from './evaluationContext/evaluationContext.types.js';

export interface CustomFeatureMetadata extends FeatureMetadata {
flagsmithId?: number;
export * from './evaluationContext/evaluationContext.types.js';

export enum SegmentSource {
API = 'api',
IDENTITY_OVERRIDE = 'identity_override'
}

// Feature types
export interface SDKFeatureMetadata extends FeatureMetadata {
id: number;
}

export interface FeatureContextWithMetadata<T extends FeatureMetadata = FeatureMetadata>
Expand All @@ -29,14 +38,6 @@ export type FeaturesWithMetadata<T extends FeatureMetadata = FeatureMetadata> =
[k: string]: FeatureContextWithMetadata<T>;
};

export interface GenericEvaluationContext<T extends FeatureMetadata = FeatureMetadata> {
environment: EnvironmentContext;
identity?: IdentityContext | null;
segments?: Segments;
features?: FeaturesWithMetadata<T>;
[k: string]: unknown;
}

export type FlagResultWithMetadata<T extends FeatureMetadata = FeatureMetadata> = FlagResult & {
metadata: T;
};
Expand All @@ -46,19 +47,50 @@ export type EvaluationResultFlags<T extends FeatureMetadata = FeatureMetadata> =
FlagResultWithMetadata<T>
>;

export type EvaluationResultSegments = EvaluationContextResult['segments'];
// Segment types
export interface SDKSegmentMetadata extends SegmentMetadata {
id?: number;
source?: SegmentSource;
}

export type EvaluationResult<T extends FeatureMetadata = FeatureMetadata> = {
flags: EvaluationResultFlags<T>;
segments: EvaluationResultSegments;
export interface SegmentContextWithMetadata<T extends SegmentMetadata = SegmentMetadata>
extends SegmentContext {
metadata: T;
overrides?: FeatureContextWithMetadata<FeatureMetadata>[];
}

export type SegmentsWithMetadata<T extends SegmentMetadata = SegmentMetadata> = {
[k: string]: SegmentContextWithMetadata<T>;
};

export type EvaluationResultWithMetadata = EvaluationResult<CustomFeatureMetadata>;
export type EvaluationContextWithMetadata = GenericEvaluationContext<CustomFeatureMetadata>;
export interface SegmentResultWithMetadata {
name: string;
metadata: SDKSegmentMetadata;
}

export enum SegmentSource {
API = 'api',
IDENTITY_OVERRIDE = 'identity_override'
export type EvaluationResultSegments = SegmentResultWithMetadata[];

// Evaluation context types
export interface GenericEvaluationContext<
T extends FeatureMetadata = FeatureMetadata,
S extends SegmentMetadata = SegmentMetadata
> {
environment: EnvironmentContext;
identity?: IdentityContext | null;
segments?: SegmentsWithMetadata<S>;
features?: FeaturesWithMetadata<T>;
[k: string]: unknown;
}

export * from './evaluationContext/evaluationContext.types.js';
export type EvaluationContextWithMetadata = GenericEvaluationContext<
SDKFeatureMetadata,
SDKSegmentMetadata
>;

// Evaluation result types
export type EvaluationResult<T extends FeatureMetadata = FeatureMetadata> = {
flags: EvaluationResultFlags<T>;
segments: EvaluationResultSegments;
};

export type EvaluationResultWithMetadata = EvaluationResult<SDKFeatureMetadata>;
Loading