Skip to content
Open
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
19 changes: 12 additions & 7 deletions flagsmith-engine/evaluation/evaluationContext/mappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ 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(),
Expand All @@ -75,16 +74,18 @@ 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: {
flagsmithId: fs.feature.id
}
}))
: [],
metadata: {
source: SegmentSource.API,
flagsmith_id: segment.id
flagsmithId: segment.id
}
};
}
Expand Down Expand Up @@ -115,11 +116,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 Down Expand Up @@ -147,7 +153,6 @@ 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(),
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
70 changes: 51 additions & 19 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 * from './evaluationContext/evaluationContext.types.js';

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

// Feature types
export interface CustomFeatureMetadata extends FeatureMetadata {
flagsmithId?: number;
flagsmithId: 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>
>;

// Segment types
export interface CustomSegmentMetadata extends SegmentMetadata {
flagsmithId: number;
source?: SegmentSource;
}

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 interface SegmentResultWithMetadata {
name: string;
metadata: CustomSegmentMetadata;
}

export type EvaluationResultSegments = EvaluationContextResult['segments'];

// 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 type EvaluationContextWithMetadata = GenericEvaluationContext<
CustomFeatureMetadata,
CustomSegmentMetadata
>;

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

export type EvaluationResultWithMetadata = EvaluationResult<CustomFeatureMetadata>;
export type EvaluationContextWithMetadata = GenericEvaluationContext<CustomFeatureMetadata>;

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

export * from './evaluationContext/evaluationContext.types.js';
12 changes: 5 additions & 7 deletions flagsmith-engine/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
CustomFeatureMetadata,
FlagResultWithMetadata
} from './evaluation/models.js';
import { getIdentitySegments } from './segments/evaluators.js';
import { getIdentitySegments, getIdentityKey } from './segments/evaluators.js';
import { EvaluationResultFlags } from './evaluation/models.js';
import { TARGETING_REASONS } from './features/types.js';
import { getHashedPercentageForObjIds } from './utils/hashing/index.js';
Expand Down Expand Up @@ -62,7 +62,6 @@ export function evaluateSegments(context: EvaluationContextWithMetadata): {
const identitySegments = getIdentitySegments(context);

const segments = identitySegments.map(segment => ({
key: segment.key,
name: segment.name,
...(segment.metadata
? {
Expand Down Expand Up @@ -96,7 +95,7 @@ export function processSegmentOverrides(identitySegments: any[]): Record<string,

for (const override of overridesList) {
if (shouldApplyOverride(override, segmentOverrides)) {
segmentOverrides[override.feature_key] = {
segmentOverrides[override.name] = {
feature: override,
segmentName: segment.name
};
Expand Down Expand Up @@ -126,16 +125,15 @@ export function evaluateFeatures(
const flags: EvaluationResultFlags<CustomFeatureMetadata> = {};

for (const feature of Object.values(context.features || {})) {
const segmentOverride = segmentOverrides[feature.feature_key];
const segmentOverride = segmentOverrides[feature.name];
const finalFeature = segmentOverride ? segmentOverride.feature : feature;
const hasOverride = !!segmentOverride;

const { value: evaluatedValue, reason: evaluatedReason } = hasOverride
? { value: finalFeature.value, reason: undefined }
: evaluateFeatureValue(finalFeature, context.identity?.key);
: evaluateFeatureValue(finalFeature, getIdentityKey(context));

flags[finalFeature.name] = {
feature_key: finalFeature.feature_key,
name: finalFeature.name,
enabled: finalFeature.enabled,
value: evaluatedValue,
Expand Down Expand Up @@ -198,7 +196,7 @@ export function shouldApplyOverride(
override: any,
existingOverrides: Record<string, SegmentOverride>
): boolean {
const currentOverride = existingOverrides[override.feature_key];
const currentOverride = existingOverrides[override.name];
return (
!currentOverride || isHigherPriority(override.priority, currentOverride.feature.priority)
);
Expand Down
8 changes: 7 additions & 1 deletion flagsmith-engine/segments/evaluators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export function traitsMatchSegmentCondition(
): boolean {
if (condition.operator === PERCENTAGE_SPLIT) {
const contextValueKey =
getContextValue(condition.property, context) || context?.identity?.key;
getContextValue(condition.property, context) || getIdentityKey(context);
const hashedPercentage = getHashedPercentageForObjIds([segmentKey, contextValueKey]);
return hashedPercentage <= parseFloat(String(condition.value));
}
Expand Down Expand Up @@ -173,3 +173,9 @@ export function getContextValue(jsonPath: string, context?: GenericEvaluationCon
return undefined;
}
}

export function getIdentityKey(context?: GenericEvaluationContext): string | undefined {
if (!context?.identity) return undefined;

return context.identity.key || `${context.environment.key}_${context.identity.identifier}`;
}
Loading