flagsmith-nodejs 1.1.0 → 2.0.0-beta.2

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.
Files changed (126) hide show
  1. package/.github/workflows/pull_request.yaml +33 -0
  2. package/.gitmodules +3 -0
  3. package/.husky/pre-commit +6 -0
  4. package/{LICENCE.md → LICENCE} +4 -5
  5. package/README.md +1 -1
  6. package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
  7. package/build/flagsmith-engine/environments/integrations/models.js +8 -0
  8. package/build/flagsmith-engine/environments/models.d.ts +25 -0
  9. package/build/flagsmith-engine/environments/models.js +40 -0
  10. package/build/flagsmith-engine/environments/util.d.ts +3 -0
  11. package/build/flagsmith-engine/environments/util.js +19 -0
  12. package/build/flagsmith-engine/features/constants.d.ts +4 -0
  13. package/build/flagsmith-engine/features/constants.js +7 -0
  14. package/build/flagsmith-engine/features/models.d.ts +32 -0
  15. package/build/flagsmith-engine/features/models.js +85 -0
  16. package/build/flagsmith-engine/features/util.d.ts +3 -0
  17. package/build/flagsmith-engine/features/util.js +20 -0
  18. package/build/flagsmith-engine/identities/models.d.ts +15 -0
  19. package/build/flagsmith-engine/identities/models.js +47 -0
  20. package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
  21. package/build/flagsmith-engine/identities/traits/models.js +12 -0
  22. package/build/flagsmith-engine/identities/util.d.ts +4 -0
  23. package/build/flagsmith-engine/identities/util.js +22 -0
  24. package/build/flagsmith-engine/index.d.ts +8 -0
  25. package/build/flagsmith-engine/index.js +61 -0
  26. package/build/flagsmith-engine/organisations/models.d.ts +9 -0
  27. package/build/flagsmith-engine/organisations/models.js +21 -0
  28. package/build/flagsmith-engine/organisations/util.d.ts +2 -0
  29. package/build/flagsmith-engine/organisations/util.js +8 -0
  30. package/build/flagsmith-engine/projects/models.d.ts +10 -0
  31. package/build/flagsmith-engine/projects/models.js +17 -0
  32. package/build/flagsmith-engine/projects/util.d.ts +2 -0
  33. package/build/flagsmith-engine/projects/util.js +15 -0
  34. package/build/flagsmith-engine/segments/constants.d.ts +26 -0
  35. package/build/flagsmith-engine/segments/constants.js +31 -0
  36. package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
  37. package/build/flagsmith-engine/segments/evaluators.js +29 -0
  38. package/build/flagsmith-engine/segments/models.d.ts +31 -0
  39. package/build/flagsmith-engine/segments/models.js +83 -0
  40. package/build/flagsmith-engine/segments/util.d.ts +4 -0
  41. package/build/flagsmith-engine/segments/util.js +23 -0
  42. package/build/flagsmith-engine/utils/collections.d.ts +4 -0
  43. package/build/flagsmith-engine/utils/collections.js +16 -0
  44. package/build/flagsmith-engine/utils/errors.d.ts +2 -0
  45. package/build/flagsmith-engine/utils/errors.js +6 -0
  46. package/build/flagsmith-engine/utils/hashing/index.d.ts +9 -0
  47. package/build/flagsmith-engine/utils/hashing/index.js +54 -0
  48. package/build/flagsmith-engine/utils/index.d.ts +1 -0
  49. package/build/flagsmith-engine/utils/index.js +14 -0
  50. package/build/index.d.ts +1 -0
  51. package/build/index.js +11 -0
  52. package/build/sdk/analytics.d.ts +28 -0
  53. package/build/sdk/analytics.js +60 -0
  54. package/build/sdk/errors.d.ts +4 -0
  55. package/build/sdk/errors.js +9 -0
  56. package/build/sdk/index.d.ts +98 -0
  57. package/build/sdk/index.js +215 -0
  58. package/build/sdk/models.d.ts +55 -0
  59. package/build/sdk/models.js +102 -0
  60. package/build/sdk/polling_manager.d.ts +9 -0
  61. package/build/sdk/polling_manager.js +31 -0
  62. package/build/sdk/utils.d.ts +12 -0
  63. package/build/sdk/utils.js +45 -0
  64. package/example/README.md +0 -6
  65. package/example/package-lock.json +1070 -2
  66. package/example/package.json +4 -3
  67. package/example/server/api/index.js +18 -18
  68. package/example/server/index.js +4 -6
  69. package/flagsmith-engine/environments/integrations/models.ts +4 -0
  70. package/flagsmith-engine/environments/models.ts +50 -0
  71. package/flagsmith-engine/environments/util.ts +29 -0
  72. package/flagsmith-engine/features/constants.ts +4 -0
  73. package/flagsmith-engine/features/models.ts +105 -0
  74. package/flagsmith-engine/features/util.ts +38 -0
  75. package/flagsmith-engine/identities/models.ts +60 -0
  76. package/flagsmith-engine/identities/traits/models.ts +9 -0
  77. package/flagsmith-engine/identities/util.ts +30 -0
  78. package/flagsmith-engine/index.ts +93 -0
  79. package/flagsmith-engine/organisations/models.ts +25 -0
  80. package/flagsmith-engine/organisations/util.ts +11 -0
  81. package/flagsmith-engine/projects/models.ts +22 -0
  82. package/flagsmith-engine/projects/util.ts +18 -0
  83. package/flagsmith-engine/segments/constants.ts +31 -0
  84. package/flagsmith-engine/segments/evaluators.ts +72 -0
  85. package/flagsmith-engine/segments/models.ts +103 -0
  86. package/flagsmith-engine/segments/util.ts +29 -0
  87. package/flagsmith-engine/utils/collections.ts +14 -0
  88. package/flagsmith-engine/utils/errors.ts +1 -0
  89. package/flagsmith-engine/utils/hashing/index.ts +52 -0
  90. package/flagsmith-engine/utils/index.ts +10 -0
  91. package/index.ts +6 -0
  92. package/jest.config.js +5 -0
  93. package/package.json +28 -12
  94. package/sdk/analytics.ts +60 -0
  95. package/sdk/errors.ts +2 -0
  96. package/sdk/index.ts +270 -0
  97. package/sdk/models.ts +145 -0
  98. package/sdk/polling_manager.ts +31 -0
  99. package/sdk/utils.ts +45 -0
  100. package/tests/engine/e2e/engine.test.ts +51 -0
  101. package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +12393 -0
  102. package/tests/engine/engine-tests/engine-test-data/readme.md +30 -0
  103. package/tests/engine/unit/egine.test.ts +96 -0
  104. package/tests/engine/unit/environments/builder.test.ts +148 -0
  105. package/tests/engine/unit/environments/models.test.ts +49 -0
  106. package/tests/engine/unit/features/models.test.ts +72 -0
  107. package/tests/engine/unit/identities/identities_builders.test.ts +85 -0
  108. package/tests/engine/unit/identities/identities_models.test.ts +105 -0
  109. package/tests/engine/unit/organization/models.test.ts +12 -0
  110. package/tests/engine/unit/segments/segments_model.test.ts +101 -0
  111. package/tests/engine/unit/segments/util.ts +151 -0
  112. package/tests/engine/unit/utils.ts +114 -0
  113. package/tests/index.js +0 -0
  114. package/tests/sdk/analytics.test.ts +43 -0
  115. package/tests/sdk/data/environment.json +33 -0
  116. package/tests/sdk/data/flags.json +20 -0
  117. package/tests/sdk/data/identities.json +29 -0
  118. package/tests/sdk/flagsmith.test.ts +184 -0
  119. package/tests/sdk/polling.test.ts +34 -0
  120. package/tests/sdk/utils.ts +39 -0
  121. package/tsconfig.json +19 -0
  122. package/.idea/codeStyles/Project.xml +0 -52
  123. package/.idea/codeStyles/codeStyleConfig.xml +0 -5
  124. package/flagsmith-core.js +0 -241
  125. package/index.d.ts +0 -85
  126. package/index.js +0 -3
@@ -1,10 +1,10 @@
1
1
  {
2
- "name": "node-minimal",
2
+ "name": "flagsmith-nodejs-example",
3
3
  "version": "0.1.1",
4
- "description": "Node Express Boilerplate",
4
+ "description": "An example using the flagsmith nodejs library",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "start": "node --inspect ./server",
7
+ "start": "cd ../ && npm run build && cd ./example && node --inspect ./server",
8
8
  "dev": "nodemon --exec npm start",
9
9
  "test": "nodemon ./server/tests/"
10
10
  },
@@ -15,6 +15,7 @@
15
15
  "npm": "3.10.x"
16
16
  },
17
17
  "dependencies": {
18
+ "flagsmith-nodejs": "^2.0.0-beta.1",
18
19
  "node-cache": "^5.1.2",
19
20
  "ssg-node-express": "4.16.4",
20
21
  "ssg-util": "0.0.3"
@@ -1,32 +1,32 @@
1
1
  const Router = require('express').Router;
2
- const environmentID = 'uCDQzKWgejrutqSYYsKWen';
3
- const flagsmith = require('../../../');
4
- const NodeCache = require('node-cache');
5
-
6
- flagsmith.init({
7
- environmentID
8
- // this is an example of a user-defined cache
9
- /*cache: new NodeCache({
10
- stdTTL: 5
11
- })*/
2
+ const Flagsmith = require('../../../build');
3
+ const environmentKey = '';
4
+ if (!environmentKey) {
5
+ throw new Error(
6
+ 'Please generate a Server Side SDK Key in environment settings to run the example'
7
+ );
8
+ }
9
+ const flagsmith = new Flagsmith({
10
+ environmentKey,
11
+ enableLocalEvaluation: true,
12
+ defaultFlagHandler: str => {
13
+ return { enabled: false, isDefault: true, value: null };
14
+ }
12
15
  });
13
16
 
14
17
  module.exports = () => {
15
18
  const api = Router();
16
19
 
17
20
  api.get('/', async (req, res) => {
18
- const font_size = await flagsmith.getValue('font_size');
19
- res.json({ font_size });
20
- });
21
-
22
- api.get('/flags', async (req, res) => {
23
- const flags = await flagsmith.getFlags();
21
+ const flags = await flagsmith.getEnvironmentFlags();
24
22
  res.json(flags);
25
23
  });
26
24
 
27
25
  api.get('/:user', async (req, res) => {
28
- const font_size = await flagsmith.getValue('font_size', req.params.user);
29
- res.json({ font_size });
26
+ const flags = await flagsmith.getIdentityFlags(req.params.user, { checkout_v2: 1 });
27
+ const fontSize = flags.getFeatureValue('font_size');
28
+ const checkoutV2 = flags.isFeatureEnabled('checkout_v2');
29
+ res.json({ fontSize, checkoutV2 });
30
30
  });
31
31
 
32
32
  return api;
@@ -12,17 +12,15 @@ app.server = http.createServer(app);
12
12
  app.use(bodyParser.json());
13
13
 
14
14
  // api router
15
- app.use('/', api());
15
+ app.use('/api', api());
16
16
 
17
17
  app.server.listen(PORT);
18
18
  console.log('Server started on port ' + app.server.address().port);
19
19
  console.log();
20
- console.log('Go to http://localhost:' + PORT + '/');
21
- console.log('To get an example feature state');
20
+ console.log('Go to http://localhost:' + PORT + '/api');
21
+ console.log('To get an example response for getFlags');
22
22
  console.log();
23
- console.log('Go to http://localhost:' + PORT + '/flagsmith_sample_user');
23
+ console.log('Go to http://localhost:' + PORT + '/api/flagsmith_sample_user');
24
24
  console.log('To get an example feature state for a user');
25
- console.log('Go to http://localhost:' + PORT + '/flags');
26
- console.log('To get an example response for getFlags');
27
25
 
28
26
  module.exports = app;
@@ -0,0 +1,4 @@
1
+ export class IntegrationModel {
2
+ api_key?: string = undefined;
3
+ base_url?: string = undefined;
4
+ }
@@ -0,0 +1,50 @@
1
+ import { FeatureStateModel } from '../features/models';
2
+ import { ProjectModel } from '../projects/models';
3
+ import { IntegrationModel } from './integrations/models';
4
+
5
+ export class EnvironmentAPIKeyModel {
6
+ id: number;
7
+ key: string;
8
+ createdAt: number;
9
+ name: string;
10
+ clientApiKey: string;
11
+ expiresAt?: number;
12
+ active = true;
13
+
14
+ constructor(
15
+ id: number,
16
+ key: string,
17
+ createdAt: number,
18
+ name: string,
19
+ clientApiKey: string,
20
+ expiresAt?: number
21
+ ) {
22
+ this.id = id;
23
+ this.key = key;
24
+ this.createdAt = createdAt;
25
+ this.name = name;
26
+ this.clientApiKey = clientApiKey;
27
+ this.expiresAt = expiresAt;
28
+ }
29
+
30
+ isValid() {
31
+ return !!this.active && (!this.expiresAt || this.expiresAt > Date.now());
32
+ }
33
+ }
34
+
35
+ export class EnvironmentModel {
36
+ id: number;
37
+ apiKey: string;
38
+ project: ProjectModel;
39
+ featureStates: FeatureStateModel[] = [];
40
+ amplitude_config?: IntegrationModel;
41
+ segment_config?: IntegrationModel;
42
+ mixpanel_config?: IntegrationModel;
43
+ heap_config?: IntegrationModel;
44
+
45
+ constructor(id: number, apiKey: string, project: ProjectModel) {
46
+ this.id = id;
47
+ this.apiKey = apiKey;
48
+ this.project = project;
49
+ }
50
+ }
@@ -0,0 +1,29 @@
1
+ import { buildFeatureStateModel } from '../features/util';
2
+ import { buildProjectModel } from '../projects/util';
3
+ import { EnvironmentAPIKeyModel, EnvironmentModel } from './models';
4
+
5
+ export function buildEnvironmentModel(environmentJSON: any) {
6
+ const project = buildProjectModel(environmentJSON.project);
7
+ const featureStates = environmentJSON.feature_states.map((fs: any) =>
8
+ buildFeatureStateModel(fs)
9
+ );
10
+ const environmentModel = new EnvironmentModel(
11
+ environmentJSON.id,
12
+ environmentJSON.api_key,
13
+ project
14
+ );
15
+ environmentModel.featureStates = featureStates;
16
+ return environmentModel;
17
+ }
18
+
19
+ export function buildEnvironmentAPIKeyModel(apiKeyJSON: any): EnvironmentAPIKeyModel {
20
+ const model = new EnvironmentAPIKeyModel(
21
+ apiKeyJSON.id,
22
+ apiKeyJSON.key,
23
+ Date.parse(apiKeyJSON.created_at),
24
+ apiKeyJSON.name,
25
+ apiKeyJSON.client_api_key
26
+ );
27
+
28
+ return model;
29
+ }
@@ -0,0 +1,4 @@
1
+ export const CONSTANTS = {
2
+ STANDARD: 'STANDARD',
3
+ MULTIVARIATE: 'MULTIVARIATE'
4
+ };
@@ -0,0 +1,105 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { getHashedPercentateForObjIds } from '../utils/hashing';
3
+
4
+ export class FeatureModel {
5
+ id: number;
6
+ name: string;
7
+ type: string;
8
+
9
+ constructor(id: number, name: string, type: string) {
10
+ this.id = id;
11
+ this.name = name;
12
+ this.type = type;
13
+ }
14
+
15
+ eq(other: FeatureModel) {
16
+ return !!other && this.id === other.id;
17
+ }
18
+ }
19
+
20
+ export class MultivariateFeatureOptionModel {
21
+ value: any;
22
+ id: number | undefined;
23
+
24
+ constructor(value: any, id?: number) {
25
+ this.value = value;
26
+ this.id = id;
27
+ }
28
+ }
29
+
30
+ export class MultivariateFeatureStateValueModel {
31
+ multivariateFeatureOption: MultivariateFeatureOptionModel;
32
+ percentageAllocation: number;
33
+ id: number;
34
+ mvFsValueUuid: string = uuidv4();
35
+
36
+ constructor(
37
+ multivariate_feature_option: MultivariateFeatureOptionModel,
38
+ percentage_allocation: number,
39
+ id: number,
40
+ mvFsValueUuid?: string
41
+ ) {
42
+ this.id = id;
43
+ this.percentageAllocation = percentage_allocation;
44
+ this.multivariateFeatureOption = multivariate_feature_option;
45
+ this.mvFsValueUuid = mvFsValueUuid || this.mvFsValueUuid;
46
+ }
47
+ }
48
+
49
+ export class FeatureStateModel {
50
+ feature: FeatureModel;
51
+ enabled: boolean;
52
+ djangoID: number;
53
+ featurestateUUID: string = uuidv4();
54
+ private value: any;
55
+ multivariateFeatureStateValues: MultivariateFeatureStateValueModel[] = [];
56
+
57
+ constructor(
58
+ feature: FeatureModel,
59
+ enabled: boolean,
60
+ djangoID: number,
61
+ value?: any,
62
+ featurestateUuid: string = uuidv4()
63
+ ) {
64
+ this.feature = feature;
65
+ this.enabled = enabled;
66
+ this.djangoID = djangoID;
67
+ this.value = value;
68
+ this.featurestateUUID = featurestateUuid;
69
+ }
70
+
71
+ setValue(value: any) {
72
+ this.value = value;
73
+ }
74
+
75
+ getValue(identityId?: number | string) {
76
+ if (!!identityId && this.multivariateFeatureStateValues.length > 0) {
77
+ return this.getMultivariateValue(identityId);
78
+ }
79
+ return this.value;
80
+ }
81
+
82
+ get_feature_state_value() {
83
+ return this.getValue();
84
+ }
85
+
86
+ getMultivariateValue(identityID: number | string) {
87
+ const percentageValue = getHashedPercentateForObjIds([
88
+ this.djangoID || this.featurestateUUID,
89
+ identityID
90
+ ]);
91
+
92
+ let startPercentage = 0;
93
+ const sortedF = this.multivariateFeatureStateValues.sort((a, b) =>
94
+ !!(a.id && b.id) ? a.id - b.id : a.mvFsValueUuid > b.mvFsValueUuid ? -1 : 1
95
+ );
96
+ for (const myValue of sortedF) {
97
+ const limit = myValue.percentageAllocation + startPercentage;
98
+ if (startPercentage <= percentageValue && percentageValue < limit) {
99
+ return myValue.multivariateFeatureOption.value;
100
+ }
101
+ startPercentage = limit;
102
+ }
103
+ return this.value;
104
+ }
105
+ }
@@ -0,0 +1,38 @@
1
+ import {
2
+ FeatureModel,
3
+ FeatureStateModel,
4
+ MultivariateFeatureOptionModel,
5
+ MultivariateFeatureStateValueModel
6
+ } from './models';
7
+
8
+ export function buildFeatureModel(featuresModelJSON: any): FeatureModel {
9
+ return new FeatureModel(featuresModelJSON.id, featuresModelJSON.name, featuresModelJSON.type);
10
+ }
11
+
12
+ export function buildFeatureStateModel(featuresStateModelJSON: any): FeatureStateModel {
13
+ const featureStateModel = new FeatureStateModel(
14
+ buildFeatureModel(featuresStateModelJSON.feature),
15
+ featuresStateModelJSON.enabled,
16
+ featuresStateModelJSON.django_id,
17
+ featuresStateModelJSON.feature_state_value,
18
+ featuresStateModelJSON.uuid
19
+ );
20
+
21
+ const multivariateFeatureStateValues = featuresStateModelJSON.multivariate_feature_state_values
22
+ ? featuresStateModelJSON.multivariate_feature_state_values.map((fsv: any) => {
23
+ const featureOption = new MultivariateFeatureOptionModel(
24
+ fsv.multivariate_feature_option.value,
25
+ fsv.multivariate_feature_option.id
26
+ );
27
+ return new MultivariateFeatureStateValueModel(
28
+ featureOption,
29
+ fsv.percentage_allocation,
30
+ fsv.id
31
+ );
32
+ })
33
+ : [];
34
+
35
+ featureStateModel.multivariateFeatureStateValues = multivariateFeatureStateValues;
36
+
37
+ return featureStateModel;
38
+ }
@@ -0,0 +1,60 @@
1
+ import { FeatureStateModel } from '../features/models';
2
+ import { IdentityFeaturesList } from '../utils/collections';
3
+ import { TraitModel } from './traits/models';
4
+
5
+ const { v4: uuidv4 } = require('uuid');
6
+
7
+ export class IdentityModel {
8
+ identifier: string;
9
+ environmentApiKey: string;
10
+ createdDate?: number;
11
+ identityFeatures: IdentityFeaturesList;
12
+ identityTraits: TraitModel[];
13
+ identityUuid: string;
14
+ djangoID: number | undefined;
15
+
16
+ constructor(
17
+ created_date: string,
18
+ identityTraits: TraitModel[],
19
+ identityFeatures: IdentityFeaturesList,
20
+ environmentApiKey: string,
21
+ identifier: string,
22
+ identityUuid?: string
23
+ ) {
24
+ this.identityUuid = identityUuid || uuidv4();
25
+ this.createdDate = Date.parse(created_date) || Date.now();
26
+ this.identityTraits = identityTraits;
27
+ this.identityFeatures = new IdentityFeaturesList(...identityFeatures);
28
+ this.environmentApiKey = environmentApiKey;
29
+ this.identifier = identifier;
30
+ }
31
+
32
+ get compositeKey() {
33
+ return IdentityModel.generateCompositeKey(this.environmentApiKey, this.identifier);
34
+ }
35
+
36
+ static generateCompositeKey(env_key: string, identifier: string) {
37
+ return `${env_key}_${identifier}`;
38
+ }
39
+
40
+ update_traits(traits: TraitModel[]) {
41
+ const existingTraits: Map<string, TraitModel> = new Map();
42
+ for (const trait of this.identityTraits) {
43
+ existingTraits.set(trait.traitKey, trait);
44
+ }
45
+
46
+ for (const trait of traits) {
47
+ if (!!trait.traitValue) {
48
+ existingTraits.set(trait.traitKey, trait);
49
+ } else {
50
+ existingTraits.delete(trait.traitKey);
51
+ }
52
+ }
53
+
54
+ this.identityTraits = [];
55
+
56
+ for (const [k, v] of existingTraits.entries()) {
57
+ this.identityTraits.push(v);
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,9 @@
1
+ export class TraitModel {
2
+ traitKey: string;
3
+ traitValue: any;
4
+
5
+ constructor(key: string, value: any) {
6
+ this.traitKey = key;
7
+ this.traitValue = value;
8
+ }
9
+ }
@@ -0,0 +1,30 @@
1
+ import { buildFeatureStateModel } from '../features/util';
2
+ import { IdentityFeaturesList } from '../utils/collections';
3
+ import { IdentityModel } from './models';
4
+ import { TraitModel } from './traits/models';
5
+
6
+ export function buildTraitModel(traitJSON: any): TraitModel {
7
+ return new TraitModel(traitJSON.trait_key, traitJSON.trait_value);
8
+ }
9
+
10
+ export function buildIdentityModel(identityJSON: any): IdentityModel {
11
+ const featureList = identityJSON.identity_features
12
+ ? new IdentityFeaturesList(
13
+ ...identityJSON.identity_features.map((f: any) => buildFeatureStateModel(f))
14
+ )
15
+ : [];
16
+
17
+ const model = new IdentityModel(
18
+ identityJSON.created_date,
19
+ identityJSON.identity_traits
20
+ ? identityJSON.identity_traits.map((trait: any) => buildTraitModel(trait))
21
+ : [],
22
+ featureList,
23
+ identityJSON.environment_api_key,
24
+ identityJSON.identifier,
25
+ identityJSON.identity_uuid
26
+ );
27
+
28
+ model.djangoID = identityJSON.django_id;
29
+ return model;
30
+ }
@@ -0,0 +1,93 @@
1
+ import { EnvironmentModel } from './environments/models';
2
+ import { FeatureStateModel } from './features/models';
3
+ import { IdentityModel } from './identities/models';
4
+ import { TraitModel } from './identities/traits/models';
5
+ import { getIdentitySegments } from './segments/evaluators';
6
+ import { SegmentModel } from './segments/models';
7
+ import { FeatureStateNotFound } from './utils/errors';
8
+
9
+ function getIdentityFeatureStatesDict(
10
+ environment: EnvironmentModel,
11
+ identity: IdentityModel,
12
+ overrideTraits?: TraitModel[]
13
+ ) {
14
+ // Get feature states from the environment
15
+ const featureStates: { [key: number]: FeatureStateModel } = {};
16
+ for (const fs of environment.featureStates) {
17
+ featureStates[fs.feature.id] = fs;
18
+ }
19
+
20
+ // Override with any feature states defined by matching segments
21
+ const identitySegments: SegmentModel[] = getIdentitySegments(
22
+ environment,
23
+ identity,
24
+ overrideTraits
25
+ );
26
+ for (const matchingSegment of identitySegments) {
27
+ for (const featureState of matchingSegment.featureStates) {
28
+ // note that feature states are stored on the segment in descending priority
29
+ // order so we only care that the last one is added
30
+ // TODO: can we optimise this?
31
+ featureStates[featureState.feature.id] = featureState;
32
+ }
33
+ }
34
+
35
+ // Override with any feature states defined directly the identity
36
+ for (const fs of identity.identityFeatures || []) {
37
+ if (featureStates[fs.feature.id]) {
38
+ featureStates[fs.feature.id] = fs;
39
+ }
40
+ }
41
+ return featureStates;
42
+ }
43
+
44
+ export function getIdentityFeatureState(
45
+ environment: EnvironmentModel,
46
+ identity: IdentityModel,
47
+ featureName: string,
48
+ overrideTraits?: TraitModel[]
49
+ ): FeatureStateModel {
50
+ const featureStates = getIdentityFeatureStatesDict(environment, identity, overrideTraits);
51
+
52
+ const matchingFeature = Object.values(featureStates).filter(
53
+ f => f.feature.name === featureName
54
+ );
55
+
56
+ if (matchingFeature.length === 0) {
57
+ throw new FeatureStateNotFound('Feature State Not Found');
58
+ }
59
+
60
+ return matchingFeature[0];
61
+ }
62
+
63
+ export function getIdentityFeatureStates(
64
+ environment: EnvironmentModel,
65
+ identity: IdentityModel,
66
+ overrideTraits?: TraitModel[]
67
+ ): FeatureStateModel[] {
68
+ const featureStates = Object.values(
69
+ getIdentityFeatureStatesDict(environment, identity, overrideTraits)
70
+ );
71
+
72
+ if (environment.project.hideDisabledFlags) {
73
+ return featureStates.filter(fs => !!fs.enabled);
74
+ }
75
+ return featureStates;
76
+ }
77
+
78
+ export function getEnvironmentFeatureState(environment: EnvironmentModel, featureName: string) {
79
+ const featuresStates = environment.featureStates.filter(f => f.feature.name === featureName);
80
+
81
+ if (featuresStates.length === 0) {
82
+ throw new Error('Feature State Not Found');
83
+ }
84
+
85
+ return featuresStates[0];
86
+ }
87
+
88
+ export function getEnvironmentFeatureStates(environment: EnvironmentModel): FeatureStateModel[] {
89
+ if (environment.project.hideDisabledFlags) {
90
+ return environment.featureStates.filter(fs => !!fs.enabled);
91
+ }
92
+ return environment.featureStates;
93
+ }
@@ -0,0 +1,25 @@
1
+ export class OrganisationModel {
2
+ id: number;
3
+ name: string;
4
+ featureAnalytics: boolean;
5
+ stopServingFlags: boolean;
6
+ persistTraitData: boolean;
7
+
8
+ constructor(
9
+ id: number,
10
+ name: string,
11
+ featureAnalytics: boolean,
12
+ stopServingFlags: boolean,
13
+ persistTraitData: boolean
14
+ ) {
15
+ this.id = id;
16
+ this.name = name;
17
+ this.featureAnalytics = featureAnalytics;
18
+ this.stopServingFlags = stopServingFlags;
19
+ this.persistTraitData = persistTraitData;
20
+ }
21
+
22
+ get unique_slug() {
23
+ return this.id.toString() + '-' + this.name;
24
+ }
25
+ }
@@ -0,0 +1,11 @@
1
+ import { OrganisationModel } from './models';
2
+
3
+ export function buildOrganizationModel(organizationJSON: any): OrganisationModel {
4
+ return new OrganisationModel(
5
+ organizationJSON.id,
6
+ organizationJSON.name,
7
+ organizationJSON.feature_analytics,
8
+ organizationJSON.stop_serving_flags,
9
+ organizationJSON.persist_trait_data
10
+ );
11
+ }
@@ -0,0 +1,22 @@
1
+ import { OrganisationModel } from '../organisations/models';
2
+ import { SegmentModel } from '../segments/models';
3
+
4
+ export class ProjectModel {
5
+ id: number;
6
+ name: string;
7
+ organisation: OrganisationModel;
8
+ hideDisabledFlags: boolean;
9
+ segments: SegmentModel[] = [];
10
+
11
+ constructor(
12
+ id: number,
13
+ name: string,
14
+ hideDisabledFlags: boolean,
15
+ organization: OrganisationModel
16
+ ) {
17
+ this.id = id;
18
+ this.name = name;
19
+ this.hideDisabledFlags = hideDisabledFlags;
20
+ this.organisation = organization;
21
+ }
22
+ }
@@ -0,0 +1,18 @@
1
+ import { buildOrganizationModel } from '../organisations/util';
2
+ import { SegmentModel } from '../segments/models';
3
+ import { buildSegmentModel } from '../segments/util';
4
+ import { ProjectModel } from './models';
5
+
6
+ export function buildProjectModel(projectJSON: any): ProjectModel {
7
+ const segments: SegmentModel[] = projectJSON['segments']
8
+ ? projectJSON['segments'].map((s: any) => buildSegmentModel(s))
9
+ : [];
10
+ const model = new ProjectModel(
11
+ projectJSON.id,
12
+ projectJSON.name,
13
+ projectJSON.hide_disabled_flags,
14
+ buildOrganizationModel(projectJSON.organisation)
15
+ );
16
+ model.segments = segments;
17
+ return model;
18
+ }
@@ -0,0 +1,31 @@
1
+ // Segment Rules
2
+ export const ALL_RULE = 'ALL';
3
+ export const ANY_RULE = 'ANY';
4
+ export const NONE_RULE = 'NONE';
5
+
6
+ export const RULE_TYPES = [ALL_RULE, ANY_RULE, NONE_RULE];
7
+
8
+ // Segment Condition Operators
9
+ export const EQUAL = 'EQUAL';
10
+ export const GREATER_THAN = 'GREATER_THAN';
11
+ export const LESS_THAN = 'LESS_THAN';
12
+ export const LESS_THAN_INCLUSIVE = 'LESS_THAN_INCLUSIVE';
13
+ export const CONTAINS = 'CONTAINS';
14
+ export const GREATER_THAN_INCLUSIVE = 'GREATER_THAN_INCLUSIVE';
15
+ export const NOT_CONTAINS = 'NOT_CONTAINS';
16
+ export const NOT_EQUAL = 'NOT_EQUAL';
17
+ export const REGEX = 'REGEX';
18
+ export const PERCENTAGE_SPLIT = 'PERCENTAGE_SPLIT';
19
+
20
+ export const CONDITION_OPERATORS = {
21
+ EQUAL,
22
+ GREATER_THAN,
23
+ LESS_THAN,
24
+ LESS_THAN_INCLUSIVE,
25
+ CONTAINS,
26
+ GREATER_THAN_INCLUSIVE,
27
+ NOT_CONTAINS,
28
+ NOT_EQUAL,
29
+ REGEX,
30
+ PERCENTAGE_SPLIT
31
+ };