flagsmith-nodejs 2.0.0-beta.4 → 2.0.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/pull_request.yaml +37 -29
- package/.idea/flagsmith-nodejs-client.iml +12 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +7 -0
- package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
- package/build/flagsmith-engine/environments/integrations/models.js +11 -0
- package/build/flagsmith-engine/environments/models.d.ts +25 -0
- package/build/flagsmith-engine/environments/models.js +29 -0
- package/build/flagsmith-engine/environments/util.d.ts +3 -0
- package/build/flagsmith-engine/environments/util.js +21 -0
- package/build/flagsmith-engine/features/constants.d.ts +4 -0
- package/build/flagsmith-engine/features/constants.js +7 -0
- package/build/flagsmith-engine/features/models.d.ts +31 -0
- package/build/flagsmith-engine/features/models.js +99 -0
- package/build/flagsmith-engine/features/util.d.ts +3 -0
- package/build/flagsmith-engine/features/util.js +20 -0
- package/build/flagsmith-engine/identities/models.d.ts +15 -0
- package/build/flagsmith-engine/identities/models.js +112 -0
- package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
- package/build/flagsmith-engine/identities/traits/models.js +11 -0
- package/build/flagsmith-engine/identities/util.d.ts +4 -0
- package/build/flagsmith-engine/identities/util.js +46 -0
- package/build/flagsmith-engine/index.d.ts +8 -0
- package/build/flagsmith-engine/index.js +113 -0
- package/build/flagsmith-engine/organisations/models.d.ts +9 -0
- package/build/flagsmith-engine/organisations/models.js +21 -0
- package/build/flagsmith-engine/organisations/util.d.ts +2 -0
- package/build/flagsmith-engine/organisations/util.js +8 -0
- package/build/flagsmith-engine/projects/models.d.ts +10 -0
- package/build/flagsmith-engine/projects/models.js +14 -0
- package/build/flagsmith-engine/projects/util.d.ts +2 -0
- package/build/flagsmith-engine/projects/util.js +15 -0
- package/build/flagsmith-engine/segments/constants.d.ts +26 -0
- package/build/flagsmith-engine/segments/constants.js +31 -0
- package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
- package/build/flagsmith-engine/segments/evaluators.js +37 -0
- package/build/flagsmith-engine/segments/models.d.ts +31 -0
- package/build/flagsmith-engine/segments/models.js +89 -0
- package/build/flagsmith-engine/segments/util.d.ts +4 -0
- package/build/flagsmith-engine/segments/util.js +25 -0
- package/build/flagsmith-engine/utils/collections.d.ts +3 -0
- package/build/flagsmith-engine/utils/collections.js +26 -0
- package/build/flagsmith-engine/utils/errors.d.ts +2 -0
- package/build/flagsmith-engine/utils/errors.js +26 -0
- package/build/flagsmith-engine/utils/hashing/index.d.ts +9 -0
- package/build/flagsmith-engine/utils/hashing/index.js +60 -0
- package/build/flagsmith-engine/utils/index.d.ts +1 -0
- package/build/flagsmith-engine/utils/index.js +14 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +11 -0
- package/build/sdk/analytics.d.ts +28 -0
- package/build/sdk/analytics.js +102 -0
- package/build/sdk/errors.d.ts +4 -0
- package/build/sdk/errors.js +34 -0
- package/build/sdk/index.d.ts +123 -0
- package/build/sdk/index.js +484 -0
- package/build/sdk/models.d.ts +55 -0
- package/build/sdk/models.js +149 -0
- package/build/sdk/polling_manager.d.ts +9 -0
- package/build/sdk/polling_manager.js +72 -0
- package/build/sdk/types.d.ts +7 -0
- package/build/sdk/types.js +2 -0
- package/build/sdk/utils.d.ts +12 -0
- package/build/sdk/utils.js +94 -0
- package/example/package-lock.json +1 -996
- package/flagsmith-engine/features/models.ts +0 -4
- package/flagsmith-engine/identities/models.ts +1 -1
- package/flagsmith-engine/index.ts +1 -1
- package/flagsmith-engine/organisations/models.ts +1 -1
- package/flagsmith-engine/segments/models.ts +0 -2
- package/flagsmith-engine/utils/collections.ts +1 -12
- package/flagsmith-engine/utils/hashing/index.ts +5 -2
- package/package.json +3 -2
- package/sdk/analytics.ts +0 -2
- package/sdk/index.ts +83 -26
- package/sdk/polling_manager.ts +0 -1
- package/sdk/types.ts +8 -0
- package/tests/engine/unit/{egine.test.ts → engine.test.ts} +20 -0
- package/tests/engine/unit/features/models.test.ts +7 -3
- package/tests/engine/unit/identities/identities_builders.test.ts +13 -0
- package/tests/engine/unit/identities/identities_models.test.ts +3 -14
- package/tests/engine/unit/organization/models.test.ts +1 -1
- package/tests/engine/unit/segments/segments_model.test.ts +2 -1
- package/tests/engine/unit/utils.ts +1 -1
- package/tests/sdk/analytics.test.ts +11 -0
- package/tests/sdk/flagsmith-cache.test.ts +150 -0
- package/tests/sdk/flagsmith-environment-flags.test.ts +197 -0
- package/tests/sdk/flagsmith-identity-flags.test.ts +140 -0
- package/tests/sdk/flagsmith.test.ts +100 -85
- package/tests/sdk/polling.test.ts +25 -0
- package/tests/sdk/utils.ts +21 -2
- package/tsconfig.json +2 -1
- package/.tool-versions +0 -1
- package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +0 -12393
- package/tests/engine/engine-tests/engine-test-data/readme.md +0 -30
|
@@ -79,10 +79,6 @@ export class FeatureStateModel {
|
|
|
79
79
|
return this.value;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
get_feature_state_value() {
|
|
83
|
-
return this.getValue();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
82
|
getMultivariateValue(identityID: number | string) {
|
|
87
83
|
const percentageValue = getHashedPercentateForObjIds([
|
|
88
84
|
this.djangoID || this.featurestateUUID,
|
|
@@ -37,7 +37,7 @@ export class IdentityModel {
|
|
|
37
37
|
return `${env_key}_${identifier}`;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
updateTraits(traits: TraitModel[]) {
|
|
41
41
|
const existingTraits: Map<string, TraitModel> = new Map();
|
|
42
42
|
for (const trait of this.identityTraits) {
|
|
43
43
|
existingTraits.set(trait.traitKey, trait);
|
|
@@ -79,7 +79,7 @@ export function getEnvironmentFeatureState(environment: EnvironmentModel, featur
|
|
|
79
79
|
const featuresStates = environment.featureStates.filter(f => f.feature.name === featureName);
|
|
80
80
|
|
|
81
81
|
if (featuresStates.length === 0) {
|
|
82
|
-
throw new
|
|
82
|
+
throw new FeatureStateNotFound('Feature State Not Found');
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
return featuresStates[0];
|
|
@@ -23,8 +23,6 @@ export const matchingFunctions = {
|
|
|
23
23
|
[CONDITION_OPERATORS.NOT_EQUAL]: (thisValue: any, otherValue: any) => thisValue != otherValue,
|
|
24
24
|
[CONDITION_OPERATORS.CONTAINS]: (thisValue: any, otherValue: any) =>
|
|
25
25
|
otherValue.includes(thisValue),
|
|
26
|
-
[CONDITION_OPERATORS.NOT_CONTAINS]: (thisValue: any, otherValue: any) =>
|
|
27
|
-
!otherValue.includes(thisValue)
|
|
28
26
|
};
|
|
29
27
|
|
|
30
28
|
export class SegmentConditionModel {
|
|
@@ -1,14 +1,3 @@
|
|
|
1
1
|
import { FeatureStateModel } from '../features/models';
|
|
2
2
|
|
|
3
|
-
export class IdentityFeaturesList extends Array<FeatureStateModel> {
|
|
4
|
-
public push(...e: FeatureStateModel[]): number {
|
|
5
|
-
for (const [_, item] of e.entries()) {
|
|
6
|
-
for (const [k, v] of this.entries()) {
|
|
7
|
-
if (v.djangoID === item.djangoID) {
|
|
8
|
-
throw new Error('feature state for this feature already exists');
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
return super.push(...e);
|
|
13
|
-
}
|
|
14
|
-
}
|
|
3
|
+
export class IdentityFeaturesList extends Array<FeatureStateModel> {}
|
|
@@ -39,12 +39,15 @@ function h2d(s: any): string {
|
|
|
39
39
|
* @returns number number between 0 (inclusive) and 100 (exclusive)
|
|
40
40
|
*/
|
|
41
41
|
export function getHashedPercentateForObjIds(objectIds: Array<any>, iterations = 1): number {
|
|
42
|
-
let
|
|
43
|
-
const hashedValue = md5(
|
|
42
|
+
let toHash = makeRepeated(objectIds, iterations).join(',');
|
|
43
|
+
const hashedValue = md5(toHash);
|
|
44
44
|
const hashedInt = bigInt(h2d(hashedValue));
|
|
45
45
|
const value = (hashedInt.mod(9999).toJSNumber() / 9998) * 100;
|
|
46
46
|
|
|
47
|
+
// we ignore this for it's nearly impossible use case to catch
|
|
48
|
+
/* istanbul ignore next */
|
|
47
49
|
if (value === 100) {
|
|
50
|
+
/* istanbul ignore next */
|
|
48
51
|
return getHashedPercentateForObjIds(objectIds, iterations + 1);
|
|
49
52
|
}
|
|
50
53
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flagsmith-nodejs",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.7",
|
|
4
4
|
"description": "Flagsmith lets you manage features flags and remote config across web, mobile and server side applications. Deliver true Continuous Integration. Get builds out faster. Control who has access to new features.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"test:watch": "jest --coverage --watch --coverageReporters='text'",
|
|
47
47
|
"test:debug": "node --inspect-brk node_modules/.bin/jest --coverage --watch --coverageReporters='text'",
|
|
48
48
|
"build": "tsc",
|
|
49
|
+
"prepublish": "npm run build",
|
|
49
50
|
"prepare": "husky install"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
@@ -66,4 +67,4 @@
|
|
|
66
67
|
"ts-jest": "^27.1.3",
|
|
67
68
|
"typescript": "^4.6.2"
|
|
68
69
|
}
|
|
69
|
-
}
|
|
70
|
+
}
|
package/sdk/analytics.ts
CHANGED
|
@@ -5,8 +5,6 @@ const ANALYTICS_ENDPOINT = 'analytics/flags/';
|
|
|
5
5
|
// Used to control how often we send data(in seconds)
|
|
6
6
|
const ANALYTICS_TIMER = 10;
|
|
7
7
|
|
|
8
|
-
const delay = (ms: number) => new Promise(resolve => setTimeout(() => resolve(undefined), ms));
|
|
9
|
-
|
|
10
8
|
export class AnalyticsProcessor {
|
|
11
9
|
private analyticsEndpoint: string;
|
|
12
10
|
private environmentKey: string;
|
package/sdk/index.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { EnvironmentDataPollingManager } from './polling_manager';
|
|
|
12
12
|
import { generateIdentitiesData, retryFetch } from './utils';
|
|
13
13
|
import { SegmentModel } from '../flagsmith-engine/segments/models';
|
|
14
14
|
import { getIdentitySegments } from '../flagsmith-engine/segments/evaluators';
|
|
15
|
+
import { FlagsmithCache } from './types';
|
|
15
16
|
|
|
16
17
|
const DEFAULT_API_URL = 'https://api.flagsmith.com/api/v1/';
|
|
17
18
|
|
|
@@ -26,12 +27,16 @@ export class Flagsmith {
|
|
|
26
27
|
enableAnalytics: boolean = false;
|
|
27
28
|
defaultFlagHandler?: (featureName: string) => DefaultFlag;
|
|
28
29
|
|
|
30
|
+
|
|
29
31
|
environmentFlagsUrl: string;
|
|
30
32
|
identitiesUrl: string;
|
|
31
33
|
environmentUrl: string;
|
|
32
34
|
|
|
33
35
|
environmentDataPollingManager?: EnvironmentDataPollingManager;
|
|
34
36
|
environment!: EnvironmentModel;
|
|
37
|
+
|
|
38
|
+
private cache?: FlagsmithCache;
|
|
39
|
+
private onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void;
|
|
35
40
|
private analyticsProcessor?: AnalyticsProcessor;
|
|
36
41
|
/**
|
|
37
42
|
* A Flagsmith client.
|
|
@@ -73,6 +78,8 @@ export class Flagsmith {
|
|
|
73
78
|
retries?: number;
|
|
74
79
|
enableAnalytics?: boolean;
|
|
75
80
|
defaultFlagHandler?: (featureName: string) => DefaultFlag;
|
|
81
|
+
cache?: FlagsmithCache,
|
|
82
|
+
onEnvironmentChange?: (error: Error | null, result: EnvironmentModel) => void,
|
|
76
83
|
}) {
|
|
77
84
|
this.environmentKey = data.environmentKey;
|
|
78
85
|
this.apiUrl = data.apiUrl || this.apiUrl;
|
|
@@ -88,6 +95,20 @@ export class Flagsmith {
|
|
|
88
95
|
this.environmentFlagsUrl = `${this.apiUrl}flags/`;
|
|
89
96
|
this.identitiesUrl = `${this.apiUrl}identities/`;
|
|
90
97
|
this.environmentUrl = `${this.apiUrl}environment-document/`;
|
|
98
|
+
this.onEnvironmentChange = data.onEnvironmentChange;
|
|
99
|
+
|
|
100
|
+
if (!!data.cache) {
|
|
101
|
+
const missingMethods: string[] = ['has', 'get', 'set'].filter(method => data.cache && !data.cache[method]);
|
|
102
|
+
|
|
103
|
+
if (missingMethods.length > 0) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Please implement the following methods in your cache: ${missingMethods.join(
|
|
106
|
+
', '
|
|
107
|
+
)}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
this.cache = data.cache;
|
|
111
|
+
}
|
|
91
112
|
|
|
92
113
|
if (this.enableLocalEvaluation) {
|
|
93
114
|
if (!this.environmentKey.startsWith('ser.')) {
|
|
@@ -105,10 +126,10 @@ export class Flagsmith {
|
|
|
105
126
|
|
|
106
127
|
this.analyticsProcessor = data.enableAnalytics
|
|
107
128
|
? new AnalyticsProcessor({
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
129
|
+
environmentKey: this.environmentKey,
|
|
130
|
+
baseApiUrl: this.apiUrl,
|
|
131
|
+
timeout: this.requestTimeoutSeconds
|
|
132
|
+
})
|
|
112
133
|
: undefined;
|
|
113
134
|
}
|
|
114
135
|
/**
|
|
@@ -117,8 +138,19 @@ export class Flagsmith {
|
|
|
117
138
|
* @returns Flags object holding all the flags for the current environment.
|
|
118
139
|
*/
|
|
119
140
|
async getEnvironmentFlags(): Promise<Flags> {
|
|
141
|
+
const cachedItem = !!this.cache && await this.cache.get(`flags`);
|
|
142
|
+
if (!!cachedItem) {
|
|
143
|
+
return cachedItem;
|
|
144
|
+
}
|
|
145
|
+
if (this.enableLocalEvaluation) {
|
|
146
|
+
return new Promise(resolve =>
|
|
147
|
+
this.environmentPromise!.then(() => {
|
|
148
|
+
resolve(this.getEnvironmentFlagsFromDocument());
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
}
|
|
120
152
|
if (this.environment) {
|
|
121
|
-
return
|
|
153
|
+
return this.getEnvironmentFlagsFromDocument();
|
|
122
154
|
}
|
|
123
155
|
|
|
124
156
|
return this.getEnvironmentFlagsFromApi();
|
|
@@ -134,7 +166,11 @@ export class Flagsmith {
|
|
|
134
166
|
Flagsmith, e.g. {"num_orders": 10}
|
|
135
167
|
* @returns Flags object holding all the flags for the given identity.
|
|
136
168
|
*/
|
|
137
|
-
getIdentityFlags(identifier: string, traits?: { [key: string]: any }): Promise<Flags> {
|
|
169
|
+
async getIdentityFlags(identifier: string, traits?: { [key: string]: any }): Promise<Flags> {
|
|
170
|
+
const cachedItem = !!this.cache && await this.cache.get(`flags-${identifier}`);
|
|
171
|
+
if (!!cachedItem) {
|
|
172
|
+
return cachedItem;
|
|
173
|
+
}
|
|
138
174
|
traits = traits || {};
|
|
139
175
|
if (this.enableLocalEvaluation) {
|
|
140
176
|
return new Promise(resolve =>
|
|
@@ -188,14 +224,23 @@ export class Flagsmith {
|
|
|
188
224
|
* You only need to call this if you wish to bypass environmentRefreshIntervalSeconds.
|
|
189
225
|
*/
|
|
190
226
|
async updateEnvironment() {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.environmentPromise
|
|
194
|
-
this.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
227
|
+
try {
|
|
228
|
+
const request = this.getEnvironmentFromApi();
|
|
229
|
+
if (!this.environmentPromise) {
|
|
230
|
+
this.environmentPromise = request.then(res => {
|
|
231
|
+
this.environment = res;
|
|
232
|
+
});
|
|
233
|
+
await this.environmentPromise;
|
|
234
|
+
} else {
|
|
235
|
+
this.environment = await request;
|
|
236
|
+
}
|
|
237
|
+
if (this.onEnvironmentChange) {
|
|
238
|
+
this.onEnvironmentChange(null, this.environment);
|
|
239
|
+
}
|
|
240
|
+
} catch (e) {
|
|
241
|
+
if (this.onEnvironmentChange) {
|
|
242
|
+
this.onEnvironmentChange(e as Error, this.environment);
|
|
243
|
+
}
|
|
199
244
|
}
|
|
200
245
|
}
|
|
201
246
|
|
|
@@ -247,15 +292,19 @@ export class Flagsmith {
|
|
|
247
292
|
return buildEnvironmentModel(environment_data);
|
|
248
293
|
}
|
|
249
294
|
|
|
250
|
-
private getEnvironmentFlagsFromDocument() {
|
|
251
|
-
|
|
295
|
+
private async getEnvironmentFlagsFromDocument(): Promise<Flags> {
|
|
296
|
+
const flags = Flags.fromFeatureStateModels({
|
|
252
297
|
featureStates: getEnvironmentFeatureStates(this.environment),
|
|
253
298
|
analyticsProcessor: this.analyticsProcessor,
|
|
254
299
|
defaultFlagHandler: this.defaultFlagHandler
|
|
255
300
|
});
|
|
301
|
+
if (!!this.cache) {
|
|
302
|
+
await this.cache.set('flags', flags);
|
|
303
|
+
}
|
|
304
|
+
return flags;
|
|
256
305
|
}
|
|
257
306
|
|
|
258
|
-
private getIdentityFlagsFromDocument(identifier: string, traits: { [key: string]: any }) {
|
|
307
|
+
private async getIdentityFlagsFromDocument(identifier: string, traits: { [key: string]: any }): Promise<Flags> {
|
|
259
308
|
const identityModel = this.buildIdentityModel(
|
|
260
309
|
identifier,
|
|
261
310
|
Object.keys(traits).map(key => ({
|
|
@@ -266,21 +315,31 @@ export class Flagsmith {
|
|
|
266
315
|
|
|
267
316
|
const featureStates = getIdentityFeatureStates(this.environment, identityModel);
|
|
268
317
|
|
|
269
|
-
|
|
318
|
+
const flags = Flags.fromFeatureStateModels({
|
|
270
319
|
featureStates: featureStates,
|
|
271
320
|
analyticsProcessor: this.analyticsProcessor,
|
|
272
321
|
defaultFlagHandler: this.defaultFlagHandler
|
|
273
322
|
});
|
|
323
|
+
|
|
324
|
+
if (!!this.cache) {
|
|
325
|
+
await this.cache.set(`flags-${identifier}`, flags);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return flags;
|
|
274
329
|
}
|
|
275
330
|
|
|
276
331
|
private async getEnvironmentFlagsFromApi() {
|
|
277
332
|
try {
|
|
278
333
|
const apiFlags = await this.getJSONResponse(this.environmentFlagsUrl, 'GET');
|
|
279
|
-
|
|
334
|
+
const flags = Flags.fromAPIFlags({
|
|
280
335
|
apiFlags: apiFlags,
|
|
281
336
|
analyticsProcessor: this.analyticsProcessor,
|
|
282
337
|
defaultFlagHandler: this.defaultFlagHandler
|
|
283
338
|
});
|
|
339
|
+
if (!!this.cache) {
|
|
340
|
+
await this.cache.set('flags', flags);
|
|
341
|
+
}
|
|
342
|
+
return flags;
|
|
284
343
|
} catch (e) {
|
|
285
344
|
if (this.defaultFlagHandler) {
|
|
286
345
|
return new Flags({
|
|
@@ -297,11 +356,15 @@ export class Flagsmith {
|
|
|
297
356
|
try {
|
|
298
357
|
const data = generateIdentitiesData(identifier, traits);
|
|
299
358
|
const jsonResponse = await this.getJSONResponse(this.identitiesUrl, 'POST', data);
|
|
300
|
-
|
|
359
|
+
const flags = Flags.fromAPIFlags({
|
|
301
360
|
apiFlags: jsonResponse['flags'],
|
|
302
361
|
analyticsProcessor: this.analyticsProcessor,
|
|
303
362
|
defaultFlagHandler: this.defaultFlagHandler
|
|
304
363
|
});
|
|
364
|
+
if (!!this.cache) {
|
|
365
|
+
await this.cache.set(`flags-${identifier}`, flags);
|
|
366
|
+
}
|
|
367
|
+
return flags;
|
|
305
368
|
} catch (e) {
|
|
306
369
|
if (this.defaultFlagHandler) {
|
|
307
370
|
return new Flags({
|
|
@@ -315,12 +378,6 @@ export class Flagsmith {
|
|
|
315
378
|
}
|
|
316
379
|
|
|
317
380
|
private buildIdentityModel(identifier: string, traits: { key: string; value: any }[]) {
|
|
318
|
-
if (!this.environment) {
|
|
319
|
-
throw new FlagsmithClientError(
|
|
320
|
-
'Unable to build identity model when no local environment present.'
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
381
|
const traitModels = traits.map(trait => new TraitModel(trait.key, trait.value));
|
|
325
382
|
return new IdentityModel('0', traitModels, [], this.environment.apiKey, identifier);
|
|
326
383
|
}
|
package/sdk/polling_manager.ts
CHANGED
|
@@ -17,7 +17,6 @@ export class EnvironmentDataPollingManager {
|
|
|
17
17
|
await this.main.updateEnvironment();
|
|
18
18
|
}, this.refreshIntervalSeconds * 1000);
|
|
19
19
|
};
|
|
20
|
-
// todo: this call should be awaited for getIdentityFlags/getEnvironmentFlags when enableLocalEvaluation is true
|
|
21
20
|
this.main.updateEnvironment();
|
|
22
21
|
updateEnvironment();
|
|
23
22
|
}
|
package/sdk/types.ts
ADDED
|
@@ -25,6 +25,12 @@ test('test_identity_get_feature_state_without_any_override', () => {
|
|
|
25
25
|
expect(feature_state.feature).toStrictEqual(feature1());
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
+
test('test_identity_get_feature_state_without_any_override_no_fs', () => {
|
|
29
|
+
expect(() => {
|
|
30
|
+
getIdentityFeatureState(environment(), identity(), 'nonExistentName');
|
|
31
|
+
}).toThrowError('Feature State Not Found');
|
|
32
|
+
});
|
|
33
|
+
|
|
28
34
|
test('test_identity_get_all_feature_states_no_segments', () => {
|
|
29
35
|
const env = environment();
|
|
30
36
|
const ident = identity();
|
|
@@ -61,6 +67,20 @@ test('test_identity_get_all_feature_states_with_traits', () => {
|
|
|
61
67
|
expect(featureStates[0].getValue()).toBe('segment_override');
|
|
62
68
|
});
|
|
63
69
|
|
|
70
|
+
test('test_identity_get_all_feature_states_with_traits_hideDisabledFlags', () => {
|
|
71
|
+
const trait_models = new TraitModel(segmentConditionProperty, segmentConditionStringValue);
|
|
72
|
+
|
|
73
|
+
const env = environmentWithSegmentOverride();
|
|
74
|
+
env.project.hideDisabledFlags = true;
|
|
75
|
+
|
|
76
|
+
const featureStates = getIdentityFeatureStates(
|
|
77
|
+
env,
|
|
78
|
+
identityInSegment(),
|
|
79
|
+
[trait_models]
|
|
80
|
+
);
|
|
81
|
+
expect(featureStates.length).toBe(0);
|
|
82
|
+
});
|
|
83
|
+
|
|
64
84
|
test('test_environment_get_all_feature_states', () => {
|
|
65
85
|
const env = environment();
|
|
66
86
|
const featureStates = getEnvironmentFeatureStates(env);
|
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
} from '../../../../flagsmith-engine/features/models';
|
|
8
8
|
import { feature1 } from '../utils';
|
|
9
9
|
|
|
10
|
+
test('test_compare_feature_model', () => {
|
|
11
|
+
const fm1 = new FeatureModel(1, 'a', 'test');
|
|
12
|
+
const fm2 = new FeatureModel(1, 'a', 'test');
|
|
13
|
+
expect(fm1.eq(fm2)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
10
16
|
test('test_initializing_feature_state_creates_default_feature_state_uuid', () => {
|
|
11
17
|
const featureState = new FeatureStateModel(feature1(), true, 1);
|
|
12
18
|
expect(featureState.featurestateUUID).toBeDefined();
|
|
@@ -65,8 +71,6 @@ test('test_feature_state_get_value_mv_values', () => {
|
|
|
65
71
|
|
|
66
72
|
mvFeatureState.setValue(mvFeatureControlValue);
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
// expect(mvFeatureState.getValue(3)).toBe(testCase[1]);
|
|
70
|
-
expect(1).toBe(1);
|
|
74
|
+
expect(mvFeatureState.getValue("test")).toBe(mvFeatureValue2);
|
|
71
75
|
}
|
|
72
76
|
});
|
|
@@ -42,6 +42,19 @@ test('test_build_identity_model_from_dictionary_uses_identity_feature_list_for_i
|
|
|
42
42
|
expect(identityModel.identityFeatures?.length).toBe(1);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
+
test('test_build_identity_model_from_dictionary_uses_identity_feature_list_for_identity_features', () => {
|
|
46
|
+
const identity_dict = {
|
|
47
|
+
id: 1,
|
|
48
|
+
identifier: 'test-identity',
|
|
49
|
+
environment_api_key: 'api-key',
|
|
50
|
+
created_date: '2021-08-22T06:25:23.406995Z',
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const identityModel = buildIdentityModel(identity_dict);
|
|
54
|
+
|
|
55
|
+
expect(identityModel.identityFeatures?.length).toBe(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
45
58
|
test('test_build_build_identity_model_from_dict_creates_identity_uuid', () => {
|
|
46
59
|
const identity_model = buildIdentityModel({
|
|
47
60
|
identifier: 'test_user',
|
|
@@ -54,7 +54,7 @@ test('test_update_traits_remove_traits_with_none_value', () => {
|
|
|
54
54
|
const trait_key = ident.identityTraits[0].traitKey;
|
|
55
55
|
const trait_to_remove = new TraitModel(trait_key, undefined);
|
|
56
56
|
|
|
57
|
-
ident.
|
|
57
|
+
ident.updateTraits([trait_to_remove]);
|
|
58
58
|
|
|
59
59
|
expect(ident.identityTraits.length).toBe(0);
|
|
60
60
|
});
|
|
@@ -66,7 +66,7 @@ test('test_update_identity_traits_updates_trait_value', () => {
|
|
|
66
66
|
const traitValue = 'updated_trait_value';
|
|
67
67
|
const traitToUpdate = new TraitModel(traitKey, traitValue);
|
|
68
68
|
|
|
69
|
-
identity.
|
|
69
|
+
identity.updateTraits([traitToUpdate]);
|
|
70
70
|
|
|
71
71
|
expect(identity.identityTraits.length).toBe(1);
|
|
72
72
|
expect(identity.identityTraits[0]).toBe(traitToUpdate);
|
|
@@ -77,7 +77,7 @@ test('test_update_traits_adds_new_traits', () => {
|
|
|
77
77
|
|
|
78
78
|
const newTrait = new TraitModel('new_key', 'foobar');
|
|
79
79
|
|
|
80
|
-
identity.
|
|
80
|
+
identity.updateTraits([newTrait]);
|
|
81
81
|
|
|
82
82
|
expect(identity.identityTraits.length).toBe(2);
|
|
83
83
|
expect(identity.identityTraits).toContain(newTrait);
|
|
@@ -92,14 +92,3 @@ test('test_append_feature_state', () => {
|
|
|
92
92
|
|
|
93
93
|
expect(ident.identityFeatures).toContain(fs1);
|
|
94
94
|
});
|
|
95
|
-
|
|
96
|
-
test('test_appending_feature_states_raises_duplicate_feature_state_if_fs_for_the_feature_already_exists', () => {
|
|
97
|
-
const ident = identityInSegment();
|
|
98
|
-
|
|
99
|
-
const fs1 = new FeatureStateModel(feature1(), false, 1);
|
|
100
|
-
const fs2 = new FeatureStateModel(feature1(), true, 1);
|
|
101
|
-
ident.identityFeatures.push(fs1);
|
|
102
|
-
expect(() => {
|
|
103
|
-
ident.identityFeatures.push(fs2);
|
|
104
|
-
}).toThrowError();
|
|
105
|
-
});
|
|
@@ -61,7 +61,8 @@ const conditionMatchCases: [string, string | number | boolean, string, boolean][
|
|
|
61
61
|
[CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'bar', false],
|
|
62
62
|
[CONDITION_OPERATORS.NOT_CONTAINS, 'bar', 'baz', true],
|
|
63
63
|
[CONDITION_OPERATORS.REGEX, 'foo', '[a-z]+', true],
|
|
64
|
-
[CONDITION_OPERATORS.REGEX, 'FOO', '[a-z]+', false]
|
|
64
|
+
[CONDITION_OPERATORS.REGEX, 'FOO', '[a-z]+', false],
|
|
65
|
+
['BAD_OP', 'a', 'a', false]
|
|
65
66
|
];
|
|
66
67
|
|
|
67
68
|
test('test_segment_condition_matches_trait_value', () => {
|
|
@@ -87,7 +87,7 @@ export function getEnvironmentFeatureStateForFeatureByName(
|
|
|
87
87
|
feature_name: string
|
|
88
88
|
): FeatureStateModel | undefined {
|
|
89
89
|
const features = environment.featureStates.filter(fs => fs.feature.name === feature_name);
|
|
90
|
-
return features
|
|
90
|
+
return features[0];
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
export function getEnvironmentFeatureStateForFeature(
|
|
@@ -36,6 +36,17 @@ test('test_analytics_processor_flush_post_request_data_match_ananlytics_data', a
|
|
|
36
36
|
});
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
jest.useFakeTimers()
|
|
40
|
+
test('test_analytics_processor_flush_post_request_data_match_ananlytics_data_test', async () => {
|
|
41
|
+
const aP = analyticsProcessor();
|
|
42
|
+
aP.trackFeature(1);
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
aP.trackFeature(2);
|
|
45
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
46
|
+
}, 15000);
|
|
47
|
+
jest.runOnlyPendingTimers();
|
|
48
|
+
});
|
|
49
|
+
|
|
39
50
|
test('test_analytics_processor_flush_early_exit_if_analytics_data_is_empty', async () => {
|
|
40
51
|
const aP = analyticsProcessor();
|
|
41
52
|
await aP.flush();
|