From cfe6101a83b51ca46968cf07cb6fcee5fef28111 Mon Sep 17 00:00:00 2001 From: wadii Date: Thu, 23 Oct 2025 16:35:13 +0200 Subject: [PATCH 1/7] feat: removed-feature-key-and-segment-key-from-schema --- .gitmodules | 2 +- .../evaluationContext.types.ts | 15 ++--- .../evaluation/evaluationContext/mappers.ts | 12 ++-- .../evaluationResult.types.ts | 10 --- flagsmith-engine/evaluation/models.ts | 40 ++++++++++-- flagsmith-engine/index.ts | 8 +-- flagsmith-engine/segments/models.ts | 62 +++++++++++-------- package.json | 4 +- sdk/models.ts | 6 +- tests/engine/engine-tests/engine-test-data | 2 +- tests/engine/unit/engine.test.ts | 47 ++++++-------- .../unit/segments/segments_model.test.ts | 11 +++- 12 files changed, 119 insertions(+), 100 deletions(-) diff --git a/.gitmodules b/.gitmodules index f9b9019..b592b35 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "tests/engine/engine-tests/engine-test-data"] path = tests/engine/engine-tests/engine-test-data url = git@github.com:Flagsmith/engine-test-data.git - branch = v2.5.0 + branch = feat/remove-feature-key-fields diff --git a/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts b/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts index d1ec209..8b6e0c3 100644 --- a/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts +++ b/flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts @@ -6,7 +6,7 @@ */ /** - * 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; /** @@ -14,7 +14,7 @@ export type Key = string; */ 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; /** @@ -22,7 +22,7 @@ export type Identifier = string; */ export type Key1 = string; /** - * Key used for % split segmentation. + * Unique segment key used for % split segmentation. */ export type Key2 = string; /** @@ -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. */ @@ -155,7 +151,7 @@ export interface EnvironmentContext { */ export interface IdentityContext { identifier: Identifier; - key: Key1; + key?: Key1; traits?: Traits; [k: string]: unknown; } @@ -214,7 +210,6 @@ export interface InSegmentCondition { */ export interface FeatureContext { key: Key3; - feature_key: FeatureKey; name: Name2; enabled: Enabled; value: Value2; diff --git a/flagsmith-engine/evaluation/evaluationContext/mappers.ts b/flagsmith-engine/evaluation/evaluationContext/mappers.ts index dede412..514bf5b 100644 --- a/flagsmith-engine/evaluation/evaluationContext/mappers.ts +++ b/flagsmith-engine/evaluation/evaluationContext/mappers.ts @@ -53,14 +53,13 @@ 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 + flagsmith_id: fs.feature.id } }; } @@ -75,11 +74,13 @@ 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: { + flagsmith_id: fs.feature.id + } })) : [], metadata: { @@ -147,13 +148,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 + flagsmith_id: fs.feature.id } })); diff --git a/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts b/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts index 390146a..5f3920b 100644 --- a/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts +++ b/flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts @@ -5,10 +5,6 @@ * and run json-schema-to-typescript to regenerate this file. */ -/** - * Unique feature identifier. - */ -export type FeatureKey = string; /** * Feature name. */ @@ -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. */ @@ -53,7 +45,6 @@ export interface Flags { [k: string]: FlagResult; } export interface FlagResult { - feature_key: FeatureKey; name: Name; enabled: Enabled; value: Value; @@ -68,7 +59,6 @@ export interface FeatureMetadata { [k: string]: unknown; } export interface SegmentResult { - key: Key; name: Name1; metadata?: SegmentMetadata; [k: string]: unknown; diff --git a/flagsmith-engine/evaluation/models.ts b/flagsmith-engine/evaluation/models.ts index 71ea56b..9a1f930 100644 --- a/flagsmith-engine/evaluation/models.ts +++ b/flagsmith-engine/evaluation/models.ts @@ -5,18 +5,25 @@ import type { EvaluationResult as EvaluationContextResult, FlagResult, - FeatureMetadata + FeatureMetadata, + SegmentMetadata } from './evaluationResult/evaluationResult.types.js'; import type { FeatureContext, EnvironmentContext, IdentityContext, - Segments + Segments, + SegmentContext } from './evaluationContext/evaluationContext.types.js'; export interface CustomFeatureMetadata extends FeatureMetadata { - flagsmithId?: number; + flagsmith_id: number; +} + +export interface CustomSegmentMetadata extends SegmentMetadata { + flagsmith_id: number; + source?: SegmentSource; } export interface FeatureContextWithMetadata @@ -29,10 +36,23 @@ export type FeaturesWithMetadata = [k: string]: FeatureContextWithMetadata; }; -export interface GenericEvaluationContext { +export interface SegmentContextWithMetadata + extends SegmentContext { + metadata: T; + overrides?: FeatureContextWithMetadata[]; +} + +export type SegmentsWithMetadata = { + [k: string]: SegmentContextWithMetadata; +}; + +export interface GenericEvaluationContext< + T extends FeatureMetadata = FeatureMetadata, + S extends SegmentMetadata = SegmentMetadata +> { environment: EnvironmentContext; identity?: IdentityContext | null; - segments?: Segments; + segments?: SegmentsWithMetadata; features?: FeaturesWithMetadata; [k: string]: unknown; } @@ -54,7 +74,15 @@ export type EvaluationResult = { }; export type EvaluationResultWithMetadata = EvaluationResult; -export type EvaluationContextWithMetadata = GenericEvaluationContext; +export type EvaluationContextWithMetadata = GenericEvaluationContext< + CustomFeatureMetadata, + CustomSegmentMetadata +>; + +export interface SegmentResultWithMetadata { + name: string; + metadata: CustomSegmentMetadata; +} export enum SegmentSource { API = 'api', diff --git a/flagsmith-engine/index.ts b/flagsmith-engine/index.ts index 534b9e1..58f9adc 100644 --- a/flagsmith-engine/index.ts +++ b/flagsmith-engine/index.ts @@ -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 ? { @@ -96,7 +95,7 @@ export function processSegmentOverrides(identitySegments: any[]): Record = {}; 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; @@ -135,7 +134,6 @@ export function evaluateFeatures( : evaluateFeatureValue(finalFeature, context.identity?.key); flags[finalFeature.name] = { - feature_key: finalFeature.feature_key, name: finalFeature.name, enabled: finalFeature.enabled, value: evaluatedValue, @@ -198,7 +196,7 @@ export function shouldApplyOverride( override: any, existingOverrides: Record ): boolean { - const currentOverride = existingOverrides[override.feature_key]; + const currentOverride = existingOverrides[override.name]; return ( !currentOverride || isHigherPriority(override.priority, currentOverride.feature.priority) ); diff --git a/flagsmith-engine/segments/models.ts b/flagsmith-engine/segments/models.ts index b912867..a3bdcbf 100644 --- a/flagsmith-engine/segments/models.ts +++ b/flagsmith-engine/segments/models.ts @@ -224,9 +224,13 @@ export class SegmentModel { if (segmentResult.metadata?.source === SegmentSource.IDENTITY_OVERRIDE) { continue; } - const segmentContext = evaluationContext.segments[segmentResult.key]; + const flagsmithId = segmentResult.metadata?.flagsmith_id; + if (!flagsmithId) { + continue; + } + const segmentContext = evaluationContext.segments[flagsmithId.toString()]; if (segmentContext) { - const segment = new SegmentModel(parseInt(segmentContext.key), segmentContext.name); + const segment = new SegmentModel(flagsmithId, segmentContext.name); segment.rules = segmentContext.rules.map(rule => new SegmentRuleModel(rule.type)); segment.featureStates = SegmentModel.createFeatureStatesFromOverrides( segmentContext.overrides || [] @@ -240,33 +244,39 @@ export class SegmentModel { private static createFeatureStatesFromOverrides(overrides: Overrides): FeatureStateModel[] { if (!overrides) return []; - return overrides.map(override => { - const feature = new FeatureModel( - parseInt(override.feature_key), - override.name, - override.variants?.length && override.variants.length > 0 - ? CONSTANTS.MULTIVARIATE - : CONSTANTS.STANDARD - ); - - const featureState = new FeatureStateModel( - feature, - override.enabled, - override.priority || 0 - ); - - if (override.value !== undefined) { - featureState.setValue(override.value); - } + return overrides + .filter(override => { + const flagsmithId = override?.metadata?.flagsmith_id; + return typeof flagsmithId === 'number'; + }) + .map(override => { + const flagsmithId = override.metadata!.flagsmith_id as number; + const feature = new FeatureModel( + flagsmithId, + override.name, + override.variants?.length && override.variants.length > 0 + ? CONSTANTS.MULTIVARIATE + : CONSTANTS.STANDARD + ); - if (override.variants && override.variants.length > 0) { - featureState.multivariateFeatureStateValues = this.createMultivariateValues( - override.variants + const featureState = new FeatureStateModel( + feature, + override.enabled, + override.priority || 0 ); - } - return featureState; - }); + if (override.value !== undefined) { + featureState.setValue(override.value); + } + + if (override.variants && override.variants.length > 0) { + featureState.multivariateFeatureStateValues = this.createMultivariateValues( + override.variants + ); + } + + return featureState; + }); } private static createMultivariateValues(variants: any[]): MultivariateFeatureStateValueModel[] { diff --git a/package.json b/package.json index dff70e4..29f0992 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,8 @@ "deploy": "npm i && npm run build && npm publish", "deploy:beta": "npm i && npm run build && npm publish --tag beta", "prepare": "husky install", - "generate-evaluation-result-types": "curl -o evaluation-result.json https://raw.githubusercontent.com/Flagsmith/flagsmith/main/sdk/evaluation-result.json && npx json2ts -i evaluation-result.json -o flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts && rm evaluation-result.json", - "generate-evaluation-context-types": "curl -o evaluation-context.json https://raw.githubusercontent.com/Flagsmith/flagsmith/main/sdk/evaluation-context.json && npx json2ts -i evaluation-context.json -o flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts && rm evaluation-context.json", + "generate-evaluation-result-types": "curl -o evaluation-result.json https://raw.githubusercontent.com/Flagsmith/flagsmith/chore%2Fevaluation-context0key/sdk/evaluation-result.json && npx json2ts -i evaluation-result.json -o flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts && rm evaluation-result.json", + "generate-evaluation-context-types": "curl -o evaluation-context.json https://raw.githubusercontent.com/Flagsmith/flagsmith/chore%2Fevaluation-context0key/sdk/evaluation-context.json && npx json2ts -i evaluation-context.json -o flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts && rm evaluation-context.json", "generate-engine-types": "npm run generate-evaluation-result-types && npm run generate-evaluation-context-types" }, "dependencies": { diff --git a/sdk/models.ts b/sdk/models.ts index f6c48a1..c7734e1 100644 --- a/sdk/models.ts +++ b/sdk/models.ts @@ -118,15 +118,15 @@ export class Flags { ): Flags { const flags: { [key: string]: Flag } = {}; for (const flag of Object.values(evaluationResult.flags)) { - const flagsmithId = flag.metadata?.flagsmithId; - if (!flagsmithId) { + const flagsmith_id = flag.metadata?.flagsmith_id; + if (!flagsmith_id) { continue; } flags[flag.name] = new Flag({ enabled: flag.enabled, value: flag.value ?? null, - featureId: flagsmithId, + featureId: flagsmith_id, featureName: flag.name, reason: flag.reason }); diff --git a/tests/engine/engine-tests/engine-test-data b/tests/engine/engine-tests/engine-test-data index 41c2021..5237fbe 160000 --- a/tests/engine/engine-tests/engine-test-data +++ b/tests/engine/engine-tests/engine-test-data @@ -1 +1 @@ -Subproject commit 41c202145e375c712600e318c439456de5b221d7 +Subproject commit 5237fbed0eb59d29165ecf3021ba04b29e11a150 diff --git a/tests/engine/unit/engine.test.ts b/tests/engine/unit/engine.test.ts index 8eb7d5d..167d8a5 100644 --- a/tests/engine/unit/engine.test.ts +++ b/tests/engine/unit/engine.test.ts @@ -30,7 +30,6 @@ test('test_get_evaluation_result_without_any_override', () => { const flag = Object.values(result.flags).find(f => f.name === feature1().name); expect(flag).toBeDefined(); expect(flag?.name).toBe(feature1().name); - expect(flag?.feature_key).toBe(feature1().id.toString()); expect(flag?.reason).toBe(TARGETING_REASONS.DEFAULT); }); @@ -112,22 +111,18 @@ test('shouldApplyOverride with priority conflicts', () => { feature1: { feature: { key: 'key', - feature_key: 'feature1', - name: 'name', + name: 'feature1', enabled: true, value: 'value', - priority: 5 + priority: 5, + metadata: { flagsmith_id: 1 } }, segmentName: 'segment1' } }; - expect(shouldApplyOverride({ feature_key: 'feature1', priority: 2 }, existingOverrides)).toBe( - true - ); - expect(shouldApplyOverride({ feature_key: 'feature1', priority: 10 }, existingOverrides)).toBe( - false - ); + expect(shouldApplyOverride({ name: 'feature1', priority: 2 }, existingOverrides)).toBe(true); + expect(shouldApplyOverride({ name: 'feature1', priority: 10 }, existingOverrides)).toBe(false); }); test('evaluateSegments handles segments with identity identifier matching', () => { @@ -176,7 +171,6 @@ test('evaluateSegments handles segments with identity identifier matching', () = overrides: [ { key: 'override1', - feature_key: 'feature1', name: 'feature1', enabled: true, value: 'overridden_value', @@ -188,21 +182,21 @@ test('evaluateSegments handles segments with identity identifier matching', () = features: { feature1: { key: 'fs1', - feature_key: 'feature1', name: 'feature1', enabled: false, - value: 'default_value' + value: 'default_value', + metadata: { flagsmith_id: 1 } } } }; - const result = evaluateSegments(context); + const result = evaluateSegments(context as any); expect(result.segments).toHaveLength(2); expect(result.segments).toEqual( expect.arrayContaining([ - { key: '1', name: 'segment_with_no_overrides' }, - { key: '2', name: 'segment_with_overrides' } + { name: 'segment_with_no_overrides' }, + { name: 'segment_with_overrides' } ]) ); @@ -239,7 +233,6 @@ test('evaluateSegments handles priority conflicts correctly', () => { overrides: [ { key: 'override1', - feature_key: 'feature1', name: 'feature1', enabled: true, value: 'low_priority_value', @@ -265,7 +258,6 @@ test('evaluateSegments handles priority conflicts correctly', () => { overrides: [ { key: 'override2', - feature_key: 'feature1', name: 'feature1', enabled: false, value: 'high_priority_value', @@ -277,15 +269,15 @@ test('evaluateSegments handles priority conflicts correctly', () => { features: { feature1: { key: 'fs1', - feature_key: 'feature1', name: 'feature1', enabled: false, - value: 'default_value' + value: 'default_value', + metadata: { flagsmith_id: 1 } } } }; - const result = evaluateSegments(context); + const result = evaluateSegments(context as any); expect(result.segments).toHaveLength(2); @@ -323,7 +315,6 @@ test('evaluateSegments with non-matching identity returns empty', () => { overrides: [ { key: 'override1', - feature_key: 'feature1', name: 'feature1', enabled: true, value: 'overridden_value' @@ -334,7 +325,7 @@ test('evaluateSegments with non-matching identity returns empty', () => { features: {} }; - const result = evaluateSegments(context); + const result = evaluateSegments(context as any); expect(result.segments).toEqual([]); expect(result.segmentOverrides).toEqual({}); @@ -345,14 +336,14 @@ test('evaluateFeatures with multivariate evaluation', () => { features: { mv_feature: { key: 'mv', - feature_key: 'mv_feature', name: 'Multivariate Feature', enabled: true, value: 'default', variants: [ - { value: 'variant_a', weight: 0 }, - { value: 'variant_b', weight: 100 } - ] + { value: 'variant_a', weight: 0, priority: 1 }, + { value: 'variant_b', weight: 100, priority: 2 } + ], + metadata: { flagsmith_id: 1 } } }, identity: { key: 'test_user', identifier: 'test_user' }, @@ -362,6 +353,6 @@ test('evaluateFeatures with multivariate evaluation', () => { } }; - const flags = evaluateFeatures(context, {}); + const flags = evaluateFeatures(context as any, {}); expect(flags['Multivariate Feature'].value).toBe('variant_b'); }); diff --git a/tests/engine/unit/segments/segments_model.test.ts b/tests/engine/unit/segments/segments_model.test.ts index 5607f03..df08f27 100644 --- a/tests/engine/unit/segments/segments_model.test.ts +++ b/tests/engine/unit/segments/segments_model.test.ts @@ -1,3 +1,4 @@ +import { SegmentSource } from '../../../../flagsmith-engine/evaluation/models'; import { EvaluationContext } from '../../../../flagsmith-engine/evaluationContext/evaluationContext.types'; import { CONSTANTS } from '../../../../flagsmith-engine/features/constants'; import { @@ -140,7 +141,7 @@ test('test_segment_rule_matching_function', () => { }); test('test_fromSegmentResult_with_multiple_variants', () => { - const segmentResults = [{ key: '1', name: 'test_segment' }]; + const segmentResults = [{ name: 'test_segment', metadata: { flagsmith_id: '1' } }]; const evaluationContext: EvaluationContext = { identity: { @@ -156,6 +157,10 @@ test('test_fromSegmentResult_with_multiple_variants', () => { '1': { key: '1', name: 'test_segment', + metadata: { + source: SegmentSource.API, + flagsmith_id: '1' + }, rules: [ { type: 'ALL', @@ -171,11 +176,13 @@ test('test_fromSegmentResult_with_multiple_variants', () => { overrides: [ { key: 'override', - feature_key: '1', name: 'multivariate_feature', enabled: true, value: 'default_value', priority: 1, + metadata: { + flagsmith_id: 1 + }, variants: [ { id: 1, value: 'variant_a', weight: 30 }, { id: 2, value: 'variant_b', weight: 70 } From b6403a93e71950b41e57a85cff5ac75103f435eb Mon Sep 17 00:00:00 2001 From: wadii Date: Fri, 24 Oct 2025 11:22:20 +0200 Subject: [PATCH 2/7] feat: re-organized-models --- flagsmith-engine/evaluation/models.ts | 64 ++++++++++++++------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/flagsmith-engine/evaluation/models.ts b/flagsmith-engine/evaluation/models.ts index 9a1f930..41aa850 100644 --- a/flagsmith-engine/evaluation/models.ts +++ b/flagsmith-engine/evaluation/models.ts @@ -13,17 +13,19 @@ import type { FeatureContext, EnvironmentContext, IdentityContext, - Segments, SegmentContext } from './evaluationContext/evaluationContext.types.js'; -export interface CustomFeatureMetadata extends FeatureMetadata { - flagsmith_id: number; +export * from './evaluationContext/evaluationContext.types.js'; + +export enum SegmentSource { + API = 'api', + IDENTITY_OVERRIDE = 'identity_override' } -export interface CustomSegmentMetadata extends SegmentMetadata { +// Feature types +export interface CustomFeatureMetadata extends FeatureMetadata { flagsmith_id: number; - source?: SegmentSource; } export interface FeatureContextWithMetadata @@ -36,6 +38,21 @@ export type FeaturesWithMetadata = [k: string]: FeatureContextWithMetadata; }; +export type FlagResultWithMetadata = FlagResult & { + metadata: T; +}; + +export type EvaluationResultFlags = Record< + string, + FlagResultWithMetadata +>; + +// Segment types +export interface CustomSegmentMetadata extends SegmentMetadata { + flagsmith_id: number; + source?: SegmentSource; +} + export interface SegmentContextWithMetadata extends SegmentContext { metadata: T; @@ -46,6 +63,14 @@ export type SegmentsWithMetadata = [k: string]: SegmentContextWithMetadata; }; +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 @@ -57,36 +82,15 @@ export interface GenericEvaluationContext< [k: string]: unknown; } -export type FlagResultWithMetadata = FlagResult & { - metadata: T; -}; - -export type EvaluationResultFlags = Record< - string, - FlagResultWithMetadata +export type EvaluationContextWithMetadata = GenericEvaluationContext< + CustomFeatureMetadata, + CustomSegmentMetadata >; -export type EvaluationResultSegments = EvaluationContextResult['segments']; - +// Evaluation result types export type EvaluationResult = { flags: EvaluationResultFlags; segments: EvaluationResultSegments; }; export type EvaluationResultWithMetadata = EvaluationResult; -export type EvaluationContextWithMetadata = GenericEvaluationContext< - CustomFeatureMetadata, - CustomSegmentMetadata ->; - -export interface SegmentResultWithMetadata { - name: string; - metadata: CustomSegmentMetadata; -} - -export enum SegmentSource { - API = 'api', - IDENTITY_OVERRIDE = 'identity_override' -} - -export * from './evaluationContext/evaluationContext.types.js'; From c144f7ab8f2b44ff746e221234e3dce24aae7d25 Mon Sep 17 00:00:00 2001 From: wadii Date: Fri, 24 Oct 2025 11:24:59 +0200 Subject: [PATCH 3/7] chore: camel-cased-flagsmith-id --- flagsmith-engine/evaluation/evaluationContext/mappers.ts | 8 ++++---- flagsmith-engine/evaluation/models.ts | 4 ++-- flagsmith-engine/segments/models.ts | 6 +++--- sdk/models.ts | 6 +++--- tests/engine/unit/engine.test.ts | 8 ++++---- tests/engine/unit/segments/segments_model.test.ts | 6 +++--- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/flagsmith-engine/evaluation/evaluationContext/mappers.ts b/flagsmith-engine/evaluation/evaluationContext/mappers.ts index 514bf5b..1fa898f 100644 --- a/flagsmith-engine/evaluation/evaluationContext/mappers.ts +++ b/flagsmith-engine/evaluation/evaluationContext/mappers.ts @@ -59,7 +59,7 @@ function mapEnvironmentModelToEvaluationContext( variants, priority: fs.featureSegment?.priority, metadata: { - flagsmith_id: fs.feature.id + flagsmithId: fs.feature.id } }; } @@ -79,13 +79,13 @@ function mapEnvironmentModelToEvaluationContext( value: fs.getValue(), priority: fs.featureSegment?.priority, metadata: { - flagsmith_id: fs.feature.id + flagsmithId: fs.feature.id } })) : [], metadata: { source: SegmentSource.API, - flagsmith_id: segment.id + flagsmithId: segment.id } }; } @@ -153,7 +153,7 @@ function mapIdentityOverridesToSegments(identityOverrides: IdentityModel[]): Seg value: fs.getValue(), priority: -Infinity, metadata: { - flagsmith_id: fs.feature.id + flagsmithId: fs.feature.id } })); diff --git a/flagsmith-engine/evaluation/models.ts b/flagsmith-engine/evaluation/models.ts index 41aa850..9b8cc06 100644 --- a/flagsmith-engine/evaluation/models.ts +++ b/flagsmith-engine/evaluation/models.ts @@ -25,7 +25,7 @@ export enum SegmentSource { // Feature types export interface CustomFeatureMetadata extends FeatureMetadata { - flagsmith_id: number; + flagsmithId: number; } export interface FeatureContextWithMetadata @@ -49,7 +49,7 @@ export type EvaluationResultFlags = // Segment types export interface CustomSegmentMetadata extends SegmentMetadata { - flagsmith_id: number; + flagsmithId: number; source?: SegmentSource; } diff --git a/flagsmith-engine/segments/models.ts b/flagsmith-engine/segments/models.ts index a3bdcbf..bd4d5b9 100644 --- a/flagsmith-engine/segments/models.ts +++ b/flagsmith-engine/segments/models.ts @@ -224,7 +224,7 @@ export class SegmentModel { if (segmentResult.metadata?.source === SegmentSource.IDENTITY_OVERRIDE) { continue; } - const flagsmithId = segmentResult.metadata?.flagsmith_id; + const flagsmithId = segmentResult.metadata?.flagsmithId; if (!flagsmithId) { continue; } @@ -246,11 +246,11 @@ export class SegmentModel { if (!overrides) return []; return overrides .filter(override => { - const flagsmithId = override?.metadata?.flagsmith_id; + const flagsmithId = override?.metadata?.flagsmithId; return typeof flagsmithId === 'number'; }) .map(override => { - const flagsmithId = override.metadata!.flagsmith_id as number; + const flagsmithId = override.metadata!.flagsmithId as number; const feature = new FeatureModel( flagsmithId, override.name, diff --git a/sdk/models.ts b/sdk/models.ts index c7734e1..f6c48a1 100644 --- a/sdk/models.ts +++ b/sdk/models.ts @@ -118,15 +118,15 @@ export class Flags { ): Flags { const flags: { [key: string]: Flag } = {}; for (const flag of Object.values(evaluationResult.flags)) { - const flagsmith_id = flag.metadata?.flagsmith_id; - if (!flagsmith_id) { + const flagsmithId = flag.metadata?.flagsmithId; + if (!flagsmithId) { continue; } flags[flag.name] = new Flag({ enabled: flag.enabled, value: flag.value ?? null, - featureId: flagsmith_id, + featureId: flagsmithId, featureName: flag.name, reason: flag.reason }); diff --git a/tests/engine/unit/engine.test.ts b/tests/engine/unit/engine.test.ts index 167d8a5..c1a60a3 100644 --- a/tests/engine/unit/engine.test.ts +++ b/tests/engine/unit/engine.test.ts @@ -115,7 +115,7 @@ test('shouldApplyOverride with priority conflicts', () => { enabled: true, value: 'value', priority: 5, - metadata: { flagsmith_id: 1 } + metadata: { flagsmithId: 1 } }, segmentName: 'segment1' } @@ -185,7 +185,7 @@ test('evaluateSegments handles segments with identity identifier matching', () = name: 'feature1', enabled: false, value: 'default_value', - metadata: { flagsmith_id: 1 } + metadata: { flagsmithId: 1 } } } }; @@ -272,7 +272,7 @@ test('evaluateSegments handles priority conflicts correctly', () => { name: 'feature1', enabled: false, value: 'default_value', - metadata: { flagsmith_id: 1 } + metadata: { flagsmithId: 1 } } } }; @@ -343,7 +343,7 @@ test('evaluateFeatures with multivariate evaluation', () => { { value: 'variant_a', weight: 0, priority: 1 }, { value: 'variant_b', weight: 100, priority: 2 } ], - metadata: { flagsmith_id: 1 } + metadata: { flagsmithId: 1 } } }, identity: { key: 'test_user', identifier: 'test_user' }, diff --git a/tests/engine/unit/segments/segments_model.test.ts b/tests/engine/unit/segments/segments_model.test.ts index df08f27..0c84448 100644 --- a/tests/engine/unit/segments/segments_model.test.ts +++ b/tests/engine/unit/segments/segments_model.test.ts @@ -141,7 +141,7 @@ test('test_segment_rule_matching_function', () => { }); test('test_fromSegmentResult_with_multiple_variants', () => { - const segmentResults = [{ name: 'test_segment', metadata: { flagsmith_id: '1' } }]; + const segmentResults = [{ name: 'test_segment', metadata: { flagsmithId: '1' } }]; const evaluationContext: EvaluationContext = { identity: { @@ -159,7 +159,7 @@ test('test_fromSegmentResult_with_multiple_variants', () => { name: 'test_segment', metadata: { source: SegmentSource.API, - flagsmith_id: '1' + flagsmithId: '1' }, rules: [ { @@ -181,7 +181,7 @@ test('test_fromSegmentResult_with_multiple_variants', () => { value: 'default_value', priority: 1, metadata: { - flagsmith_id: 1 + flagsmithId: 1 }, variants: [ { id: 1, value: 'variant_a', weight: 30 }, From 25dd97ca87696d08c7a8764a4a3525bf9105ddd0 Mon Sep 17 00:00:00 2001 From: wadii Date: Fri, 24 Oct 2025 16:14:50 +0200 Subject: [PATCH 4/7] chore: throw-error-if-metadata-missing --- sdk/models.ts | 5 ++++- tests/sdk/flagsmith.test.ts | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/sdk/models.ts b/sdk/models.ts index f6c48a1..17b0b22 100644 --- a/sdk/models.ts +++ b/sdk/models.ts @@ -120,7 +120,10 @@ export class Flags { for (const flag of Object.values(evaluationResult.flags)) { const flagsmithId = flag.metadata?.flagsmithId; if (!flagsmithId) { - continue; + throw new Error( + `FlagResult metadata.flagsmithId is missing for feature "${flag.name}". ` + + `This indicates a bug in the SDK, please report it.` + ); } flags[flag.name] = new Flag({ diff --git a/tests/sdk/flagsmith.test.ts b/tests/sdk/flagsmith.test.ts index 41c1d8c..caa80f6 100644 --- a/tests/sdk/flagsmith.test.ts +++ b/tests/sdk/flagsmith.test.ts @@ -519,3 +519,23 @@ test('get_user_agent_extracts_version_from_package_json', async () => { expect(userAgent).toBe(`flagsmith-nodejs-sdk/${packageJson.version}`); }); + +test('Flags.fromEvaluationResult throws error when metadata.flagsmithId is missing', () => { + const evaluationResult = { + flags: { + test_feature: { + enabled: true, + name: 'test_feature', + value: 'test_value', + reason: 'DEFAULT', + metadata: {} + } + }, + segments: [] + }; + + expect(() => Flags.fromEvaluationResult(evaluationResult as any)).toThrow( + 'FlagResult metadata.flagsmithId is missing for feature "test_feature". ' + + 'This indicates a bug in the SDK, please report it.' + ); +}); From 798995f11bec1e7d05aa599ce77bc89cf59d1bdc Mon Sep 17 00:00:00 2001 From: wadii Date: Fri, 24 Oct 2025 17:13:28 +0200 Subject: [PATCH 5/7] feat: boolean-trait-returns-false-in-operator --- .gitmodules | 2 +- flagsmith-engine/segments/models.ts | 3 +++ package.json | 4 ++-- tests/engine/engine-tests/engine-test-data | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index b592b35..06c9e18 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "tests/engine/engine-tests/engine-test-data"] path = tests/engine/engine-tests/engine-test-data url = git@github.com:Flagsmith/engine-test-data.git - branch = feat/remove-feature-key-fields + branch = v3.0.0 diff --git a/flagsmith-engine/segments/models.ts b/flagsmith-engine/segments/models.ts index bd4d5b9..8f3fb4b 100644 --- a/flagsmith-engine/segments/models.ts +++ b/flagsmith-engine/segments/models.ts @@ -144,6 +144,9 @@ export class SegmentConditionModel { return parsedTraitValue % divisor === remainder; }, evaluateIn: (traitValue: string[] | string) => { + if (!traitValue || typeof traitValue === 'boolean') { + return false; + } if (Array.isArray(this.value)) { return this.value.includes(traitValue.toString()); } diff --git a/package.json b/package.json index 29f0992..dff70e4 100644 --- a/package.json +++ b/package.json @@ -58,8 +58,8 @@ "deploy": "npm i && npm run build && npm publish", "deploy:beta": "npm i && npm run build && npm publish --tag beta", "prepare": "husky install", - "generate-evaluation-result-types": "curl -o evaluation-result.json https://raw.githubusercontent.com/Flagsmith/flagsmith/chore%2Fevaluation-context0key/sdk/evaluation-result.json && npx json2ts -i evaluation-result.json -o flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts && rm evaluation-result.json", - "generate-evaluation-context-types": "curl -o evaluation-context.json https://raw.githubusercontent.com/Flagsmith/flagsmith/chore%2Fevaluation-context0key/sdk/evaluation-context.json && npx json2ts -i evaluation-context.json -o flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts && rm evaluation-context.json", + "generate-evaluation-result-types": "curl -o evaluation-result.json https://raw.githubusercontent.com/Flagsmith/flagsmith/main/sdk/evaluation-result.json && npx json2ts -i evaluation-result.json -o flagsmith-engine/evaluation/evaluationResult/evaluationResult.types.ts && rm evaluation-result.json", + "generate-evaluation-context-types": "curl -o evaluation-context.json https://raw.githubusercontent.com/Flagsmith/flagsmith/main/sdk/evaluation-context.json && npx json2ts -i evaluation-context.json -o flagsmith-engine/evaluation/evaluationContext/evaluationContext.types.ts && rm evaluation-context.json", "generate-engine-types": "npm run generate-evaluation-result-types && npm run generate-evaluation-context-types" }, "dependencies": { diff --git a/tests/engine/engine-tests/engine-test-data b/tests/engine/engine-tests/engine-test-data index 5237fbe..8d19e96 160000 --- a/tests/engine/engine-tests/engine-test-data +++ b/tests/engine/engine-tests/engine-test-data @@ -1 +1 @@ -Subproject commit 5237fbed0eb59d29165ecf3021ba04b29e11a150 +Subproject commit 8d19e9627013c0e0213c29f3318fd45a179868fa From 93c261e88d15684974ba23d7bfdec8225d8cb8d1 Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Mon, 27 Oct 2025 11:25:48 +0100 Subject: [PATCH 6/7] feat: implemented-implicit-identity-key (#211) --- .gitmodules | 2 +- flagsmith-engine/evaluation/evaluationContext/mappers.ts | 9 +++++++-- flagsmith-engine/index.ts | 4 ++-- flagsmith-engine/segments/evaluators.ts | 8 +++++++- tests/engine/engine-tests/engine-test-data | 2 +- 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/.gitmodules b/.gitmodules index 06c9e18..93de146 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "tests/engine/engine-tests/engine-test-data"] path = tests/engine/engine-tests/engine-test-data url = git@github.com:Flagsmith/engine-test-data.git - branch = v3.0.0 + branch = v3.1.0 diff --git a/flagsmith-engine/evaluation/evaluationContext/mappers.ts b/flagsmith-engine/evaluation/evaluationContext/mappers.ts index 1fa898f..4593f60 100644 --- a/flagsmith-engine/evaluation/evaluationContext/mappers.ts +++ b/flagsmith-engine/evaluation/evaluationContext/mappers.ts @@ -116,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 { diff --git a/flagsmith-engine/index.ts b/flagsmith-engine/index.ts index 58f9adc..c57abd0 100644 --- a/flagsmith-engine/index.ts +++ b/flagsmith-engine/index.ts @@ -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'; @@ -131,7 +131,7 @@ export function evaluateFeatures( const { value: evaluatedValue, reason: evaluatedReason } = hasOverride ? { value: finalFeature.value, reason: undefined } - : evaluateFeatureValue(finalFeature, context.identity?.key); + : evaluateFeatureValue(finalFeature, getIdentityKey(context)); flags[finalFeature.name] = { name: finalFeature.name, diff --git a/flagsmith-engine/segments/evaluators.ts b/flagsmith-engine/segments/evaluators.ts index 0b08526..aecae7f 100644 --- a/flagsmith-engine/segments/evaluators.ts +++ b/flagsmith-engine/segments/evaluators.ts @@ -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)); } @@ -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}`; +} diff --git a/tests/engine/engine-tests/engine-test-data b/tests/engine/engine-tests/engine-test-data index 8d19e96..6ab57ec 160000 --- a/tests/engine/engine-tests/engine-test-data +++ b/tests/engine/engine-tests/engine-test-data @@ -1 +1 @@ -Subproject commit 8d19e9627013c0e0213c29f3318fd45a179868fa +Subproject commit 6ab57ec67bc84659e8b5aa41534b04fe45cc4cbe From 2e60f40bbcd303ba5108f606a1adf530cc9fd7e7 Mon Sep 17 00:00:00 2001 From: Zaimwa9 Date: Wed, 12 Nov 2025 16:07:28 +0100 Subject: [PATCH 7/7] fix: environment evaluation skip segments (#213) * feat: implemented-implicit-identity-key * fix: skip-segment-if-environment-flag-and-moved-flagsmith-id-to-id * fix: rename-custom-to-sdk-metadata --- .../evaluation/evaluationContext/mappers.ts | 32 ++++++++++++------- flagsmith-engine/evaluation/models.ts | 18 +++++------ flagsmith-engine/index.ts | 10 +++--- flagsmith-engine/segments/models.ts | 16 +++++----- sdk/index.ts | 9 +++--- sdk/models.ts | 10 +++--- tests/engine/unit/engine.test.ts | 8 ++--- .../unit/segments/segments_model.test.ts | 6 ++-- tests/sdk/flagsmith.test.ts | 4 +-- 9 files changed, 61 insertions(+), 52 deletions(-) diff --git a/flagsmith-engine/evaluation/evaluationContext/mappers.ts b/flagsmith-engine/evaluation/evaluationContext/mappers.ts index 4593f60..9ceb9ff 100644 --- a/flagsmith-engine/evaluation/evaluationContext/mappers.ts +++ b/flagsmith-engine/evaluation/evaluationContext/mappers.ts @@ -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'; @@ -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; @@ -40,7 +46,7 @@ function mapEnvironmentModelToEvaluationContext( name: environment.project.name }; - const features: FeaturesWithMetadata = {}; + const features: FeaturesWithMetadata = {}; for (const fs of environment.featureStates) { const variants = fs.multivariateFeatureStateValues?.length > 0 @@ -59,12 +65,12 @@ function mapEnvironmentModelToEvaluationContext( variants, priority: fs.featureSegment?.priority, metadata: { - flagsmithId: fs.feature.id + id: fs.feature.id } }; } - const segmentOverrides: Segments = {}; + const segmentOverrides: SegmentsWithMetadata = {}; for (const segment of environment.project.segments) { segmentOverrides[segment.id.toString()] = { key: segment.id.toString(), @@ -79,18 +85,18 @@ function mapEnvironmentModelToEvaluationContext( value: fs.getValue(), priority: fs.featureSegment?.priority, metadata: { - flagsmithId: fs.feature.id + id: fs.feature.id } })) : [], metadata: { source: SegmentSource.API, - flagsmithId: segment.id + id: segment.id } }; } - let identityOverrideSegments: Segments = {}; + let identityOverrideSegments: SegmentsWithMetadata = {}; if (environment.identityOverrides && environment.identityOverrides.length > 0) { identityOverrideSegments = mapIdentityOverridesToSegments(environment.identityOverrides); } @@ -140,8 +146,10 @@ function mapSegmentRuleModelToRule(rule: any): any { }; } -function mapIdentityOverridesToSegments(identityOverrides: IdentityModel[]): Segments { - const segments: Segments = {}; +function mapIdentityOverridesToSegments( + identityOverrides: IdentityModel[] +): SegmentsWithMetadata { + const segments: SegmentsWithMetadata = {}; const featuresToIdentifiers = new Map(); for (const identity of identityOverrides) { @@ -158,7 +166,7 @@ function mapIdentityOverridesToSegments(identityOverrides: IdentityModel[]): Seg value: fs.getValue(), priority: -Infinity, metadata: { - flagsmithId: fs.feature.id + id: fs.feature.id } })); diff --git a/flagsmith-engine/evaluation/models.ts b/flagsmith-engine/evaluation/models.ts index 9b8cc06..72f98dd 100644 --- a/flagsmith-engine/evaluation/models.ts +++ b/flagsmith-engine/evaluation/models.ts @@ -24,8 +24,8 @@ export enum SegmentSource { } // Feature types -export interface CustomFeatureMetadata extends FeatureMetadata { - flagsmithId: number; +export interface SDKFeatureMetadata extends FeatureMetadata { + id: number; } export interface FeatureContextWithMetadata @@ -48,8 +48,8 @@ export type EvaluationResultFlags = >; // Segment types -export interface CustomSegmentMetadata extends SegmentMetadata { - flagsmithId: number; +export interface SDKSegmentMetadata extends SegmentMetadata { + id?: number; source?: SegmentSource; } @@ -65,10 +65,10 @@ export type SegmentsWithMetadata = export interface SegmentResultWithMetadata { name: string; - metadata: CustomSegmentMetadata; + metadata: SDKSegmentMetadata; } -export type EvaluationResultSegments = EvaluationContextResult['segments']; +export type EvaluationResultSegments = SegmentResultWithMetadata[]; // Evaluation context types export interface GenericEvaluationContext< @@ -83,8 +83,8 @@ export interface GenericEvaluationContext< } export type EvaluationContextWithMetadata = GenericEvaluationContext< - CustomFeatureMetadata, - CustomSegmentMetadata + SDKFeatureMetadata, + SDKSegmentMetadata >; // Evaluation result types @@ -93,4 +93,4 @@ export type EvaluationResult = { segments: EvaluationResultSegments; }; -export type EvaluationResultWithMetadata = EvaluationResult; +export type EvaluationResultWithMetadata = EvaluationResult; diff --git a/flagsmith-engine/index.ts b/flagsmith-engine/index.ts index c57abd0..e3f20f1 100644 --- a/flagsmith-engine/index.ts +++ b/flagsmith-engine/index.ts @@ -3,7 +3,7 @@ import { EvaluationResultSegments, EvaluationResultWithMetadata, FeatureContextWithMetadata, - CustomFeatureMetadata, + SDKFeatureMetadata, FlagResultWithMetadata } from './evaluation/models.js'; import { getIdentitySegments, getIdentityKey } from './segments/evaluators.js'; @@ -18,7 +18,7 @@ export { FeatureModel, FeatureStateModel } from './features/models.js'; export { OrganisationModel } from './organisations/models.js'; type SegmentOverride = { - feature: FeatureContextWithMetadata; + feature: FeatureContextWithMetadata; segmentName: string; }; @@ -121,8 +121,8 @@ export function processSegmentOverrides(identitySegments: any[]): Record -): EvaluationResultFlags { - const flags: EvaluationResultFlags = {}; +): EvaluationResultFlags { + const flags: EvaluationResultFlags = {}; for (const feature of Object.values(context.features || {})) { const segmentOverride = segmentOverrides[feature.name]; @@ -141,7 +141,7 @@ export function evaluateFeatures( reason: evaluatedReason ?? getTargetingMatchReason({ type: 'SEGMENT', override: segmentOverride }) - } as FlagResultWithMetadata; + } as FlagResultWithMetadata; } return flags; diff --git a/flagsmith-engine/segments/models.ts b/flagsmith-engine/segments/models.ts index 8f3fb4b..a61c0c1 100644 --- a/flagsmith-engine/segments/models.ts +++ b/flagsmith-engine/segments/models.ts @@ -227,13 +227,13 @@ export class SegmentModel { if (segmentResult.metadata?.source === SegmentSource.IDENTITY_OVERRIDE) { continue; } - const flagsmithId = segmentResult.metadata?.flagsmithId; - if (!flagsmithId) { + const segmentMetadataId = segmentResult.metadata?.id; + if (!segmentMetadataId) { continue; } - const segmentContext = evaluationContext.segments[flagsmithId.toString()]; + const segmentContext = evaluationContext.segments[segmentMetadataId.toString()]; if (segmentContext) { - const segment = new SegmentModel(flagsmithId, segmentContext.name); + const segment = new SegmentModel(segmentMetadataId, segmentContext.name); segment.rules = segmentContext.rules.map(rule => new SegmentRuleModel(rule.type)); segment.featureStates = SegmentModel.createFeatureStatesFromOverrides( segmentContext.overrides || [] @@ -249,13 +249,13 @@ export class SegmentModel { if (!overrides) return []; return overrides .filter(override => { - const flagsmithId = override?.metadata?.flagsmithId; - return typeof flagsmithId === 'number'; + const overrideMetadataId = override?.metadata?.id; + return typeof overrideMetadataId === 'number'; }) .map(override => { - const flagsmithId = override.metadata!.flagsmithId as number; + const overrideMetadataId = override.metadata!.id as number; const feature = new FeatureModel( - flagsmithId, + overrideMetadataId, override.name, override.variants?.length && override.variants.length > 0 ? CONSTANTS.MULTIVARIATE diff --git a/sdk/index.ts b/sdk/index.ts index 81cbde7..6618ed8 100644 --- a/sdk/index.ts +++ b/sdk/index.ts @@ -25,6 +25,7 @@ import { } from './types.js'; import { pino, Logger } from 'pino'; import { getEvaluationContext } from '../flagsmith-engine/evaluation/evaluationContext/mappers.js'; +import { EvaluationContextWithMetadata } from '../flagsmith-engine/evaluation/models.js'; export { AnalyticsProcessor, AnalyticsProcessorOptions } from './analytics.js'; export { FlagsmithAPIError, FlagsmithClientError } from './errors.js'; @@ -282,7 +283,7 @@ export class Flagsmith { if (!context) { throw new FlagsmithClientError('Local evaluation required to obtain identity segments'); } - const evaluationResult = getEvaluationResult(context); + const evaluationResult = getEvaluationResult(context as EvaluationContextWithMetadata); return SegmentModel.fromSegmentResult(evaluationResult.segments, context); } @@ -449,11 +450,11 @@ export class Flagsmith { private async getEnvironmentFlagsFromDocument(): Promise { const environment = await this.getEnvironment(); - const context = getEvaluationContext(environment); + const context = getEvaluationContext(environment, undefined, undefined, true); if (!context) { throw new FlagsmithClientError('Unable to get flags. No environment present.'); } - const evaluationResult = getEvaluationResult(context); + const evaluationResult = getEvaluationResult(context as EvaluationContextWithMetadata); const flags = Flags.fromEvaluationResult(evaluationResult); if (!!this.cache) { @@ -481,7 +482,7 @@ export class Flagsmith { if (!context) { throw new FlagsmithClientError('Unable to get flags. No environment present.'); } - const evaluationResult = getEvaluationResult(context); + const evaluationResult = getEvaluationResult(context as EvaluationContextWithMetadata); const flags = Flags.fromEvaluationResult( evaluationResult, diff --git a/sdk/models.ts b/sdk/models.ts index 17b0b22..1cce57e 100644 --- a/sdk/models.ts +++ b/sdk/models.ts @@ -1,5 +1,5 @@ import { - CustomFeatureMetadata, + SDKFeatureMetadata, FlagResultWithMetadata, EvaluationResultWithMetadata } from '../flagsmith-engine/evaluation/models.js'; @@ -118,10 +118,10 @@ export class Flags { ): Flags { const flags: { [key: string]: Flag } = {}; for (const flag of Object.values(evaluationResult.flags)) { - const flagsmithId = flag.metadata?.flagsmithId; - if (!flagsmithId) { + const flagMetadataId = flag.metadata?.id; + if (!flagMetadataId) { throw new Error( - `FlagResult metadata.flagsmithId is missing for feature "${flag.name}". ` + + `FlagResult metadata.id is missing for feature "${flag.name}". ` + `This indicates a bug in the SDK, please report it.` ); } @@ -129,7 +129,7 @@ export class Flags { flags[flag.name] = new Flag({ enabled: flag.enabled, value: flag.value ?? null, - featureId: flagsmithId, + featureId: flagMetadataId, featureName: flag.name, reason: flag.reason }); diff --git a/tests/engine/unit/engine.test.ts b/tests/engine/unit/engine.test.ts index c1a60a3..1c43f8e 100644 --- a/tests/engine/unit/engine.test.ts +++ b/tests/engine/unit/engine.test.ts @@ -115,7 +115,7 @@ test('shouldApplyOverride with priority conflicts', () => { enabled: true, value: 'value', priority: 5, - metadata: { flagsmithId: 1 } + metadata: { id: 1 } }, segmentName: 'segment1' } @@ -185,7 +185,7 @@ test('evaluateSegments handles segments with identity identifier matching', () = name: 'feature1', enabled: false, value: 'default_value', - metadata: { flagsmithId: 1 } + metadata: { id: 1 } } } }; @@ -272,7 +272,7 @@ test('evaluateSegments handles priority conflicts correctly', () => { name: 'feature1', enabled: false, value: 'default_value', - metadata: { flagsmithId: 1 } + metadata: { id: 1 } } } }; @@ -343,7 +343,7 @@ test('evaluateFeatures with multivariate evaluation', () => { { value: 'variant_a', weight: 0, priority: 1 }, { value: 'variant_b', weight: 100, priority: 2 } ], - metadata: { flagsmithId: 1 } + metadata: { id: 1 } } }, identity: { key: 'test_user', identifier: 'test_user' }, diff --git a/tests/engine/unit/segments/segments_model.test.ts b/tests/engine/unit/segments/segments_model.test.ts index 0c84448..1210998 100644 --- a/tests/engine/unit/segments/segments_model.test.ts +++ b/tests/engine/unit/segments/segments_model.test.ts @@ -141,7 +141,7 @@ test('test_segment_rule_matching_function', () => { }); test('test_fromSegmentResult_with_multiple_variants', () => { - const segmentResults = [{ name: 'test_segment', metadata: { flagsmithId: '1' } }]; + const segmentResults = [{ name: 'test_segment', metadata: { id: '1' } }]; const evaluationContext: EvaluationContext = { identity: { @@ -159,7 +159,7 @@ test('test_fromSegmentResult_with_multiple_variants', () => { name: 'test_segment', metadata: { source: SegmentSource.API, - flagsmithId: '1' + id: '1' }, rules: [ { @@ -181,7 +181,7 @@ test('test_fromSegmentResult_with_multiple_variants', () => { value: 'default_value', priority: 1, metadata: { - flagsmithId: 1 + id: 1 }, variants: [ { id: 1, value: 'variant_a', weight: 30 }, diff --git a/tests/sdk/flagsmith.test.ts b/tests/sdk/flagsmith.test.ts index caa80f6..b0b66f3 100644 --- a/tests/sdk/flagsmith.test.ts +++ b/tests/sdk/flagsmith.test.ts @@ -520,7 +520,7 @@ test('get_user_agent_extracts_version_from_package_json', async () => { expect(userAgent).toBe(`flagsmith-nodejs-sdk/${packageJson.version}`); }); -test('Flags.fromEvaluationResult throws error when metadata.flagsmithId is missing', () => { +test('Flags.fromEvaluationResult throws error when metadata.id is missing', () => { const evaluationResult = { flags: { test_feature: { @@ -535,7 +535,7 @@ test('Flags.fromEvaluationResult throws error when metadata.flagsmithId is missi }; expect(() => Flags.fromEvaluationResult(evaluationResult as any)).toThrow( - 'FlagResult metadata.flagsmithId is missing for feature "test_feature". ' + + 'FlagResult metadata.id is missing for feature "test_feature". ' + 'This indicates a bug in the SDK, please report it.' ); });