flagsmith-nodejs 1.0.9 → 2.0.0-beta.1

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 (127) 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/.idea/flagsmith-nodejs-client.iml +12 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/vcs.xml +6 -0
  7. package/.prettierignore +1 -0
  8. package/.prettierrc.js +9 -0
  9. package/CONTRIBUTING.md +5 -4
  10. package/{LICENCE.md → LICENCE} +5 -6
  11. package/README.md +1 -1
  12. package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
  13. package/build/flagsmith-engine/environments/integrations/models.js +8 -0
  14. package/build/flagsmith-engine/environments/models.d.ts +25 -0
  15. package/build/flagsmith-engine/environments/models.js +40 -0
  16. package/build/flagsmith-engine/environments/util.d.ts +3 -0
  17. package/build/flagsmith-engine/environments/util.js +19 -0
  18. package/build/flagsmith-engine/features/constants.d.ts +4 -0
  19. package/build/flagsmith-engine/features/constants.js +7 -0
  20. package/build/flagsmith-engine/features/models.d.ts +32 -0
  21. package/build/flagsmith-engine/features/models.js +85 -0
  22. package/build/flagsmith-engine/features/util.d.ts +3 -0
  23. package/build/flagsmith-engine/features/util.js +20 -0
  24. package/build/flagsmith-engine/identities/models.d.ts +15 -0
  25. package/build/flagsmith-engine/identities/models.js +47 -0
  26. package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
  27. package/build/flagsmith-engine/identities/traits/models.js +12 -0
  28. package/build/flagsmith-engine/identities/util.d.ts +4 -0
  29. package/build/flagsmith-engine/identities/util.js +22 -0
  30. package/build/flagsmith-engine/index.d.ts +8 -0
  31. package/build/flagsmith-engine/index.js +60 -0
  32. package/build/flagsmith-engine/organisations/models.d.ts +9 -0
  33. package/build/flagsmith-engine/organisations/models.js +21 -0
  34. package/build/flagsmith-engine/organisations/util.d.ts +2 -0
  35. package/build/flagsmith-engine/organisations/util.js +8 -0
  36. package/build/flagsmith-engine/projects/models.d.ts +10 -0
  37. package/build/flagsmith-engine/projects/models.js +18 -0
  38. package/build/flagsmith-engine/projects/util.d.ts +2 -0
  39. package/build/flagsmith-engine/projects/util.js +15 -0
  40. package/build/flagsmith-engine/segments/constants.d.ts +26 -0
  41. package/build/flagsmith-engine/segments/constants.js +31 -0
  42. package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
  43. package/build/flagsmith-engine/segments/evaluators.js +29 -0
  44. package/build/flagsmith-engine/segments/models.d.ts +31 -0
  45. package/build/flagsmith-engine/segments/models.js +83 -0
  46. package/build/flagsmith-engine/segments/util.d.ts +4 -0
  47. package/build/flagsmith-engine/segments/util.js +23 -0
  48. package/build/flagsmith-engine/utils/collections.d.ts +4 -0
  49. package/build/flagsmith-engine/utils/collections.js +16 -0
  50. package/build/flagsmith-engine/utils/hashing/index.d.ts +1 -0
  51. package/build/flagsmith-engine/utils/hashing/index.js +56 -0
  52. package/build/flagsmith-engine/utils/index.d.ts +1 -0
  53. package/build/flagsmith-engine/utils/index.js +14 -0
  54. package/build/index.d.ts +2 -0
  55. package/build/index.js +7 -0
  56. package/build/sdk/analytics.d.ts +28 -0
  57. package/build/sdk/analytics.js +81 -0
  58. package/build/sdk/errors.d.ts +4 -0
  59. package/build/sdk/errors.js +9 -0
  60. package/build/sdk/index.d.ts +99 -0
  61. package/build/sdk/index.js +221 -0
  62. package/build/sdk/models.d.ts +55 -0
  63. package/build/sdk/models.js +102 -0
  64. package/build/sdk/polling_manager.d.ts +9 -0
  65. package/build/sdk/polling_manager.js +31 -0
  66. package/build/sdk/utils.d.ts +12 -0
  67. package/build/sdk/utils.js +45 -0
  68. package/example/README.md +8 -14
  69. package/example/package-lock.json +12 -12
  70. package/example/package.json +4 -4
  71. package/example/server/api/index.js +19 -22
  72. package/example/server/index.js +4 -9
  73. package/flagsmith-engine/environments/integrations/models.ts +4 -0
  74. package/flagsmith-engine/environments/models.ts +50 -0
  75. package/flagsmith-engine/environments/util.ts +29 -0
  76. package/flagsmith-engine/features/constants.ts +4 -0
  77. package/flagsmith-engine/features/models.ts +105 -0
  78. package/flagsmith-engine/features/util.ts +38 -0
  79. package/flagsmith-engine/identities/models.ts +60 -0
  80. package/flagsmith-engine/identities/traits/models.ts +9 -0
  81. package/flagsmith-engine/identities/util.ts +30 -0
  82. package/flagsmith-engine/index.ts +92 -0
  83. package/flagsmith-engine/organisations/models.ts +25 -0
  84. package/flagsmith-engine/organisations/util.ts +11 -0
  85. package/flagsmith-engine/projects/models.ts +23 -0
  86. package/flagsmith-engine/projects/util.ts +18 -0
  87. package/flagsmith-engine/segments/constants.ts +31 -0
  88. package/flagsmith-engine/segments/evaluators.ts +72 -0
  89. package/flagsmith-engine/segments/models.ts +103 -0
  90. package/flagsmith-engine/segments/util.ts +29 -0
  91. package/flagsmith-engine/utils/collections.ts +14 -0
  92. package/flagsmith-engine/utils/hashing/index.ts +57 -0
  93. package/flagsmith-engine/utils/index.ts +10 -0
  94. package/index.ts +3 -0
  95. package/jest.config.js +5 -0
  96. package/package.json +24 -3
  97. package/sdk/analytics.ts +88 -0
  98. package/sdk/errors.ts +2 -0
  99. package/sdk/index.ts +282 -0
  100. package/sdk/models.ts +143 -0
  101. package/sdk/polling_manager.ts +31 -0
  102. package/sdk/utils.ts +45 -0
  103. package/tests/engine/e2e/engine.test.ts +51 -0
  104. package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +12393 -0
  105. package/tests/engine/engine-tests/engine-test-data/readme.md +30 -0
  106. package/tests/engine/unit/egine.test.ts +96 -0
  107. package/tests/engine/unit/environments/builder.test.ts +148 -0
  108. package/tests/engine/unit/environments/models.test.ts +49 -0
  109. package/tests/engine/unit/features/models.test.ts +72 -0
  110. package/tests/engine/unit/identities/identities_builders.test.ts +85 -0
  111. package/tests/engine/unit/identities/identities_models.test.ts +105 -0
  112. package/tests/engine/unit/organization/models.test.ts +12 -0
  113. package/tests/engine/unit/segments/segments_model.test.ts +101 -0
  114. package/tests/engine/unit/segments/util.ts +151 -0
  115. package/tests/engine/unit/utils.ts +114 -0
  116. package/tests/index.js +0 -0
  117. package/tests/sdk/analytics.test.ts +43 -0
  118. package/tests/sdk/data/environment.json +33 -0
  119. package/tests/sdk/data/flags.json +20 -0
  120. package/tests/sdk/data/identities.json +29 -0
  121. package/tests/sdk/flagsmith.test.ts +184 -0
  122. package/tests/sdk/polling.test.ts +34 -0
  123. package/tests/sdk/utils.ts +39 -0
  124. package/tsconfig.json +19 -0
  125. package/flagsmith-core.js +0 -238
  126. package/index.d.ts +0 -78
  127. package/index.js +0 -5
@@ -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
+ created_at: number,
18
+ name: string,
19
+ client_api_key: string,
20
+ expires_at?: number
21
+ ) {
22
+ this.id = id;
23
+ this.key = key;
24
+ this.createdAt = created_at;
25
+ this.name = name;
26
+ this.clientApiKey = client_api_key;
27
+ this.expiresAt = expires_at;
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, api_key: string, project: ProjectModel) {
46
+ this.id = id;
47
+ this.apiKey = api_key;
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
+ _value: any;
55
+ multivariateFeatureStateValues: MultivariateFeatureStateValueModel[] = [];
56
+
57
+ constructor(
58
+ feature: FeatureModel,
59
+ enabled: boolean,
60
+ djangoID: number,
61
+ value?: any,
62
+ featurestate_uuid: string = uuidv4()
63
+ ) {
64
+ this.feature = feature;
65
+ this.enabled = enabled;
66
+ this.djangoID = djangoID;
67
+ this._value = value;
68
+ this.featurestateUUID = featurestate_uuid;
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
+ identity_traits: TraitModel[],
19
+ identity_features: IdentityFeaturesList,
20
+ environment_api_key: string,
21
+ identifier: string,
22
+ identity_uuid?: string
23
+ ) {
24
+ this.identityUuid = identity_uuid || uuidv4();
25
+ this.createdDate = Date.parse(created_date) || Date.now();
26
+ this.identityTraits = identity_traits;
27
+ this.identityFeatures = new IdentityFeaturesList(...identity_features);
28
+ this.environmentApiKey = environment_api_key;
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,92 @@
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
+
8
+ function getIdentityFeatureStatesDict(
9
+ environment: EnvironmentModel,
10
+ identity: IdentityModel,
11
+ override_traits?: TraitModel[]
12
+ ) {
13
+ // Get feature states from the environment
14
+ const feature_states: { [key: number]: FeatureStateModel } = {};
15
+ for (const fs of environment.featureStates) {
16
+ feature_states[fs.feature.id] = fs;
17
+ }
18
+
19
+ // Override with any feature states defined by matching segments
20
+ const identity_segments: SegmentModel[] = getIdentitySegments(
21
+ environment,
22
+ identity,
23
+ override_traits
24
+ );
25
+ for (const matching_segment of identity_segments) {
26
+ for (const feature_state of matching_segment.featureStates) {
27
+ // note that feature states are stored on the segment in descending priority
28
+ // order so we only care that the last one is added
29
+ // TODO: can we optimise this?
30
+ feature_states[feature_state.feature.id] = feature_state;
31
+ }
32
+ }
33
+
34
+ // Override with any feature states defined directly the identity
35
+ for (const fs of identity.identityFeatures || []) {
36
+ if (feature_states[fs.feature.id]) {
37
+ feature_states[fs.feature.id] = fs;
38
+ }
39
+ }
40
+ return feature_states;
41
+ }
42
+
43
+ export function getIdentityFeatureState(
44
+ environment: EnvironmentModel,
45
+ identity: IdentityModel,
46
+ feature_name: string,
47
+ override_traits?: TraitModel[]
48
+ ): FeatureStateModel {
49
+ const featureStates = getIdentityFeatureStatesDict(environment, identity, override_traits);
50
+
51
+ const matchingFeature = Object.values(featureStates).filter(
52
+ f => f.feature.name === feature_name
53
+ );
54
+
55
+ if (matchingFeature.length === 0) {
56
+ throw new Error('Feature State Not Found');
57
+ }
58
+
59
+ return matchingFeature[0];
60
+ }
61
+
62
+ export function getIdentityFeatureStates(
63
+ environment: EnvironmentModel,
64
+ identity: IdentityModel,
65
+ overrideTraits?: TraitModel[]
66
+ ): FeatureStateModel[] {
67
+ const feature_states = Object.values(
68
+ getIdentityFeatureStatesDict(environment, identity, overrideTraits)
69
+ );
70
+
71
+ if (environment.project.hideDisabledFlags) {
72
+ return feature_states.filter(fs => !!fs.enabled);
73
+ }
74
+ return feature_states;
75
+ }
76
+
77
+ export function getEnvironmentFeatureState(environment: EnvironmentModel, featureName: string) {
78
+ const featuresStates = environment.featureStates.filter(f => f.feature.name === featureName);
79
+
80
+ if (featuresStates.length === 0) {
81
+ throw new Error('Feature State Not Found');
82
+ }
83
+
84
+ return featuresStates[0];
85
+ }
86
+
87
+ export function getEnvironmentFeatureStates(environment: EnvironmentModel): FeatureStateModel[] {
88
+ if (environment.project.hideDisabledFlags) {
89
+ return environment.featureStates.filter(fs => !!fs.enabled);
90
+ }
91
+ return environment.featureStates;
92
+ }
@@ -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
+ feature_analytics: boolean,
12
+ stop_serving_flags: boolean,
13
+ persist_trait_data: boolean
14
+ ) {
15
+ this.id = id;
16
+ this.name = name;
17
+ this.featureAnalytics = feature_analytics;
18
+ this.stopServingFlags = stop_serving_flags;
19
+ this.persistTraitData = persist_trait_data;
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,23 @@
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
+ // FIXME
10
+ segments: SegmentModel[] = [];
11
+
12
+ constructor(
13
+ id: number,
14
+ name: string,
15
+ hide_disabled_flags: boolean,
16
+ organization: OrganisationModel
17
+ ) {
18
+ this.id = id;
19
+ this.name = name;
20
+ this.hideDisabledFlags = hide_disabled_flags;
21
+ this.organisation = organization;
22
+ }
23
+ }
@@ -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
+ };
@@ -0,0 +1,72 @@
1
+ import { EnvironmentModel } from '../environments/models';
2
+ import { IdentityModel } from '../identities/models';
3
+ import { TraitModel } from '../identities/traits/models';
4
+ import { getHashedPercentateForObjIds } from '../utils/hashing';
5
+ import { PERCENTAGE_SPLIT } from './constants';
6
+ import { SegmentConditionModel, SegmentModel, SegmentRuleModel } from './models';
7
+
8
+ export function getIdentitySegments(
9
+ environment: EnvironmentModel,
10
+ identity: IdentityModel,
11
+ overrideTraits?: TraitModel[]
12
+ ): SegmentModel[] {
13
+ return environment.project.segments.filter(segment =>
14
+ evaluateIdentityInSegment(identity, segment, overrideTraits)
15
+ );
16
+ }
17
+
18
+ export function evaluateIdentityInSegment(
19
+ identity: IdentityModel,
20
+ segment: SegmentModel,
21
+ overrideTraits?: TraitModel[]
22
+ ): boolean {
23
+ return (
24
+ segment.rules.length > 0 &&
25
+ segment.rules.filter(rule =>
26
+ traitsMatchSegmentRule(
27
+ overrideTraits || identity.identityTraits,
28
+ rule,
29
+ segment.id,
30
+ identity.compositeKey
31
+ )
32
+ ).length === segment.rules.length
33
+ );
34
+ }
35
+
36
+ function traitsMatchSegmentRule(
37
+ identityTraits: TraitModel[],
38
+ rule: SegmentRuleModel,
39
+ segmentId: number | string,
40
+ identityId: number | string
41
+ ): boolean {
42
+ const matchesConditions =
43
+ rule.conditions.length > 0
44
+ ? rule.matchingFunction()(
45
+ rule.conditions.map(condition =>
46
+ traitsMatchSegmentCondition(identityTraits, condition, segmentId, identityId)
47
+ )
48
+ )
49
+ : true;
50
+ return (
51
+ matchesConditions &&
52
+ rule.rules.filter(rule =>
53
+ traitsMatchSegmentRule(identityTraits, rule, segmentId, identityId)
54
+ ).length === rule.rules.length
55
+ );
56
+ }
57
+
58
+ function traitsMatchSegmentCondition(
59
+ identity_traits: TraitModel[],
60
+ condition: SegmentConditionModel,
61
+ segmentId: number | string,
62
+ identityId: number | string
63
+ ): boolean {
64
+ if (condition.operator == PERCENTAGE_SPLIT) {
65
+ return getHashedPercentateForObjIds([segmentId, identityId]) <= parseFloat(condition.value);
66
+ }
67
+
68
+ const traits = identity_traits.filter(t => t.traitKey === condition.property_);
69
+ const trait = traits.length > 0 ? traits[0] : undefined;
70
+
71
+ return trait ? condition.matchesTraitValue(trait.traitValue) : false;
72
+ }