@kenji71089/evaluation 0.0.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.
- package/.release-please-manifest.json +3 -0
- package/README.md +9 -0
- package/eslint.config.mjs +27 -0
- package/lib/clauseEvaluator.d.ts +22 -0
- package/lib/clauseEvaluator.js +299 -0
- package/lib/clauseEvaluator.mjs +214 -0
- package/lib/dependencyEvaluator.d.ts +6 -0
- package/lib/dependencyEvaluator.js +46 -0
- package/lib/dependencyEvaluator.mjs +19 -0
- package/lib/errors.d.ts +9 -0
- package/lib/errors.js +15 -0
- package/lib/errors.mjs +12 -0
- package/lib/evaluation.d.ts +25 -0
- package/lib/evaluation.js +503 -0
- package/lib/evaluation.mjs +328 -0
- package/lib/google/api/annotations_pb.d.ts +8 -0
- package/lib/google/api/annotations_pb.js +40 -0
- package/lib/google/api/annotations_pb.mjs +54 -0
- package/lib/google/api/annotations_pb_service.d.ts +3 -0
- package/lib/google/api/annotations_pb_service.js +3 -0
- package/lib/google/api/annotations_pb_service.mjs +3 -0
- package/lib/google/api/http_pb.d.ts +132 -0
- package/lib/google/api/http_pb.js +860 -0
- package/lib/google/api/http_pb.mjs +982 -0
- package/lib/google/api/http_pb_service.d.ts +3 -0
- package/lib/google/api/http_pb_service.js +3 -0
- package/lib/google/api/http_pb_service.mjs +3 -0
- package/lib/google/rpc/code_pb.d.ts +26 -0
- package/lib/google/rpc/code_pb.js +44 -0
- package/lib/google/rpc/code_pb.mjs +48 -0
- package/lib/google/rpc/code_pb_service.d.ts +3 -0
- package/lib/google/rpc/code_pb_service.js +3 -0
- package/lib/google/rpc/code_pb_service.mjs +3 -0
- package/lib/google/rpc/error_details_pb.d.ts +322 -0
- package/lib/google/rpc/error_details_pb.js +2220 -0
- package/lib/google/rpc/error_details_pb.mjs +2499 -0
- package/lib/google/rpc/error_details_pb_service.d.ts +3 -0
- package/lib/google/rpc/error_details_pb_service.js +3 -0
- package/lib/google/rpc/error_details_pb_service.mjs +3 -0
- package/lib/google/rpc/status_pb.d.ts +36 -0
- package/lib/google/rpc/status_pb.js +235 -0
- package/lib/google/rpc/status_pb.mjs +268 -0
- package/lib/google/rpc/status_pb_service.d.ts +3 -0
- package/lib/google/rpc/status_pb_service.js +3 -0
- package/lib/google/rpc/status_pb_service.mjs +3 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.js +199 -0
- package/lib/index.mjs +46 -0
- package/lib/modelFactory.d.ts +64 -0
- package/lib/modelFactory.js +206 -0
- package/lib/modelFactory.mjs +176 -0
- package/lib/proto/event/client/event_pb.d.ts +761 -0
- package/lib/proto/event/client/event_pb.js +5195 -0
- package/lib/proto/event/client/event_pb.mjs +5865 -0
- package/lib/proto/event/client/event_pb_service.d.ts +3 -0
- package/lib/proto/event/client/event_pb_service.js +3 -0
- package/lib/proto/event/client/event_pb_service.mjs +3 -0
- package/lib/proto/event/domain/event_pb.d.ts +4518 -0
- package/lib/proto/event/domain/event_pb.js +10834 -0
- package/lib/proto/event/domain/event_pb.mjs +33315 -0
- package/lib/proto/event/domain/event_pb_service.d.ts +3 -0
- package/lib/proto/event/domain/event_pb_service.js +3 -0
- package/lib/proto/event/domain/event_pb_service.mjs +3 -0
- package/lib/proto/event/domain/localized_message_pb.d.ts +29 -0
- package/lib/proto/event/domain/localized_message_pb.js +183 -0
- package/lib/proto/event/domain/localized_message_pb.mjs +206 -0
- package/lib/proto/event/domain/localized_message_pb_service.d.ts +3 -0
- package/lib/proto/event/domain/localized_message_pb_service.js +3 -0
- package/lib/proto/event/domain/localized_message_pb_service.mjs +3 -0
- package/lib/proto/event/service/feature_pb.d.ts +44 -0
- package/lib/proto/event/service/feature_pb.js +277 -0
- package/lib/proto/event/service/feature_pb.mjs +319 -0
- package/lib/proto/event/service/feature_pb_service.d.ts +3 -0
- package/lib/proto/event/service/feature_pb_service.js +3 -0
- package/lib/proto/event/service/feature_pb_service.mjs +3 -0
- package/lib/proto/event/service/segment_pb.d.ts +51 -0
- package/lib/proto/event/service/segment_pb.js +324 -0
- package/lib/proto/event/service/segment_pb.mjs +375 -0
- package/lib/proto/event/service/segment_pb_service.d.ts +3 -0
- package/lib/proto/event/service/segment_pb_service.js +3 -0
- package/lib/proto/event/service/segment_pb_service.mjs +3 -0
- package/lib/proto/event/service/user_pb.d.ts +49 -0
- package/lib/proto/event/service/user_pb.js +315 -0
- package/lib/proto/event/service/user_pb.mjs +362 -0
- package/lib/proto/event/service/user_pb_service.d.ts +3 -0
- package/lib/proto/event/service/user_pb_service.js +3 -0
- package/lib/proto/event/service/user_pb_service.mjs +3 -0
- package/lib/proto/feature/clause_pb.d.ts +57 -0
- package/lib/proto/feature/clause_pb.js +277 -0
- package/lib/proto/feature/clause_pb.mjs +312 -0
- package/lib/proto/feature/clause_pb_service.d.ts +3 -0
- package/lib/proto/feature/clause_pb_service.js +3 -0
- package/lib/proto/feature/clause_pb_service.mjs +3 -0
- package/lib/proto/feature/command_pb.d.ts +1213 -0
- package/lib/proto/feature/command_pb.js +8260 -0
- package/lib/proto/feature/command_pb.mjs +9275 -0
- package/lib/proto/feature/command_pb_service.d.ts +3 -0
- package/lib/proto/feature/command_pb_service.js +3 -0
- package/lib/proto/feature/command_pb_service.mjs +3 -0
- package/lib/proto/feature/evaluation_pb.d.ts +111 -0
- package/lib/proto/feature/evaluation_pb.js +685 -0
- package/lib/proto/feature/evaluation_pb.mjs +793 -0
- package/lib/proto/feature/evaluation_pb_service.d.ts +3 -0
- package/lib/proto/feature/evaluation_pb_service.js +3 -0
- package/lib/proto/feature/evaluation_pb_service.mjs +3 -0
- package/lib/proto/feature/feature_last_used_info_pb.d.ts +45 -0
- package/lib/proto/feature/feature_last_used_info_pb.js +283 -0
- package/lib/proto/feature/feature_last_used_info_pb.mjs +326 -0
- package/lib/proto/feature/feature_last_used_info_pb_service.d.ts +3 -0
- package/lib/proto/feature/feature_last_used_info_pb_service.js +3 -0
- package/lib/proto/feature/feature_last_used_info_pb_service.mjs +3 -0
- package/lib/proto/feature/feature_pb.d.ts +192 -0
- package/lib/proto/feature/feature_pb.js +1210 -0
- package/lib/proto/feature/feature_pb.mjs +1413 -0
- package/lib/proto/feature/feature_pb_service.d.ts +3 -0
- package/lib/proto/feature/feature_pb_service.js +3 -0
- package/lib/proto/feature/feature_pb_service.mjs +3 -0
- package/lib/proto/feature/flag_trigger_pb.d.ts +84 -0
- package/lib/proto/feature/flag_trigger_pb.js +452 -0
- package/lib/proto/feature/flag_trigger_pb.mjs +525 -0
- package/lib/proto/feature/flag_trigger_pb_service.d.ts +3 -0
- package/lib/proto/feature/flag_trigger_pb_service.js +3 -0
- package/lib/proto/feature/flag_trigger_pb_service.mjs +3 -0
- package/lib/proto/feature/prerequisite_pb.d.ts +29 -0
- package/lib/proto/feature/prerequisite_pb.js +183 -0
- package/lib/proto/feature/prerequisite_pb.mjs +206 -0
- package/lib/proto/feature/prerequisite_pb_service.d.ts +3 -0
- package/lib/proto/feature/prerequisite_pb_service.js +3 -0
- package/lib/proto/feature/prerequisite_pb_service.mjs +3 -0
- package/lib/proto/feature/reason_pb.d.ts +40 -0
- package/lib/proto/feature/reason_pb.js +196 -0
- package/lib/proto/feature/reason_pb.mjs +219 -0
- package/lib/proto/feature/reason_pb_service.d.ts +3 -0
- package/lib/proto/feature/reason_pb_service.js +3 -0
- package/lib/proto/feature/reason_pb_service.mjs +3 -0
- package/lib/proto/feature/rule_pb.d.ts +39 -0
- package/lib/proto/feature/rule_pb.js +254 -0
- package/lib/proto/feature/rule_pb.mjs +291 -0
- package/lib/proto/feature/rule_pb_service.d.ts +3 -0
- package/lib/proto/feature/rule_pb_service.js +3 -0
- package/lib/proto/feature/rule_pb_service.mjs +3 -0
- package/lib/proto/feature/segment_pb.d.ts +161 -0
- package/lib/proto/feature/segment_pb.js +974 -0
- package/lib/proto/feature/segment_pb.mjs +1127 -0
- package/lib/proto/feature/segment_pb_service.d.ts +3 -0
- package/lib/proto/feature/segment_pb_service.js +3 -0
- package/lib/proto/feature/segment_pb_service.mjs +3 -0
- package/lib/proto/feature/service_pb.d.ts +2158 -0
- package/lib/proto/feature/service_pb.js +5363 -0
- package/lib/proto/feature/service_pb.mjs +16348 -0
- package/lib/proto/feature/service_pb_service.d.ts +747 -0
- package/lib/proto/feature/service_pb_service.js +1424 -0
- package/lib/proto/feature/service_pb_service.mjs +1501 -0
- package/lib/proto/feature/strategy_pb.d.ts +110 -0
- package/lib/proto/feature/strategy_pb.js +712 -0
- package/lib/proto/feature/strategy_pb.mjs +803 -0
- package/lib/proto/feature/strategy_pb_service.d.ts +3 -0
- package/lib/proto/feature/strategy_pb_service.js +3 -0
- package/lib/proto/feature/strategy_pb_service.mjs +3 -0
- package/lib/proto/feature/target_pb.d.ts +31 -0
- package/lib/proto/feature/target_pb.js +207 -0
- package/lib/proto/feature/target_pb.mjs +232 -0
- package/lib/proto/feature/target_pb_service.d.ts +3 -0
- package/lib/proto/feature/target_pb_service.js +3 -0
- package/lib/proto/feature/target_pb_service.mjs +3 -0
- package/lib/proto/feature/variation_pb.d.ts +37 -0
- package/lib/proto/feature/variation_pb.js +233 -0
- package/lib/proto/feature/variation_pb.mjs +266 -0
- package/lib/proto/feature/variation_pb_service.d.ts +3 -0
- package/lib/proto/feature/variation_pb_service.js +3 -0
- package/lib/proto/feature/variation_pb_service.mjs +3 -0
- package/lib/proto/gateway/service_pb.d.ts +772 -0
- package/lib/proto/gateway/service_pb.js +5249 -0
- package/lib/proto/gateway/service_pb.mjs +6001 -0
- package/lib/proto/gateway/service_pb_service.d.ts +253 -0
- package/lib/proto/gateway/service_pb_service.js +436 -0
- package/lib/proto/gateway/service_pb_service.mjs +461 -0
- package/lib/proto/user/user_pb.d.ts +58 -0
- package/lib/proto/user/user_pb.js +410 -0
- package/lib/proto/user/user_pb.mjs +460 -0
- package/lib/proto/user/user_pb_service.d.ts +3 -0
- package/lib/proto/user/user_pb_service.js +3 -0
- package/lib/proto/user/user_pb_service.mjs +3 -0
- package/lib/protoc-gen-openapiv2/options/annotations_pb.d.ts +16 -0
- package/lib/protoc-gen-openapiv2/options/annotations_pb.js +100 -0
- package/lib/protoc-gen-openapiv2/options/annotations_pb.mjs +158 -0
- package/lib/protoc-gen-openapiv2/options/annotations_pb_service.d.ts +3 -0
- package/lib/protoc-gen-openapiv2/options/annotations_pb_service.js +3 -0
- package/lib/protoc-gen-openapiv2/options/annotations_pb_service.mjs +3 -0
- package/lib/protoc-gen-openapiv2/options/openapiv2_pb.d.ts +834 -0
- package/lib/protoc-gen-openapiv2/options/openapiv2_pb.js +5456 -0
- package/lib/protoc-gen-openapiv2/options/openapiv2_pb.mjs +6256 -0
- package/lib/protoc-gen-openapiv2/options/openapiv2_pb_service.d.ts +3 -0
- package/lib/protoc-gen-openapiv2/options/openapiv2_pb_service.js +3 -0
- package/lib/protoc-gen-openapiv2/options/openapiv2_pb_service.mjs +3 -0
- package/lib/ruleEvaluator.d.ts +13 -0
- package/lib/ruleEvaluator.js +80 -0
- package/lib/ruleEvaluator.mjs +41 -0
- package/lib/segmentEvaluator.d.ts +7 -0
- package/lib/segmentEvaluator.js +74 -0
- package/lib/segmentEvaluator.mjs +34 -0
- package/lib/strategyEvaluator.d.ts +10 -0
- package/lib/strategyEvaluator.js +135 -0
- package/lib/strategyEvaluator.mjs +83 -0
- package/lib/userEvaluation.d.ts +8 -0
- package/lib/userEvaluation.js +70 -0
- package/lib/userEvaluation.mjs +60 -0
- package/package.json +46 -0
- package/release-please-config.json +22 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Evaluator = void 0;
|
|
4
|
+
exports.EvaluationID = EvaluationID;
|
|
5
|
+
const errors_1 = require("./errors");
|
|
6
|
+
const clause_pb_1 = require("./proto/feature/clause_pb");
|
|
7
|
+
const evaluation_pb_1 = require("./proto/feature/evaluation_pb");
|
|
8
|
+
const reason_pb_1 = require("./proto/feature/reason_pb");
|
|
9
|
+
const variation_pb_1 = require("./proto/feature/variation_pb");
|
|
10
|
+
const ruleEvaluator_1 = require("./ruleEvaluator");
|
|
11
|
+
const strategyEvaluator_1 = require("./strategyEvaluator");
|
|
12
|
+
const userEvaluation_1 = require("./userEvaluation");
|
|
13
|
+
const modelFactory_1 = require("./modelFactory");
|
|
14
|
+
const SECONDS_TO_RE_EVALUATE_ALL = 30 * 24 * 60 * 60; // 30 days
|
|
15
|
+
const SECONDS_FOR_ADJUSTMENT = 10; // 10 seconds
|
|
16
|
+
function EvaluationID(featureID, featureVersion, userID) {
|
|
17
|
+
return `${featureID}:${featureVersion}:${userID}`;
|
|
18
|
+
}
|
|
19
|
+
class Evaluator {
|
|
20
|
+
ruleEvaluator;
|
|
21
|
+
strategyEvaluator;
|
|
22
|
+
constructor() {
|
|
23
|
+
this.ruleEvaluator = new ruleEvaluator_1.RuleEvaluator();
|
|
24
|
+
this.strategyEvaluator = new strategyEvaluator_1.StrategyEvaluator();
|
|
25
|
+
}
|
|
26
|
+
async evaluateFeatures(features, user, mapSegmentUsers, targetTag) {
|
|
27
|
+
return this.evaluate(features, user, mapSegmentUsers, false, targetTag);
|
|
28
|
+
}
|
|
29
|
+
// GetPrerequisiteDownwards gets the features specified as prerequisite by the targetFeatures.
|
|
30
|
+
getPrerequisiteDownwards(targetFeatures, allFeatures) {
|
|
31
|
+
const allFeaturesMap = new Map();
|
|
32
|
+
for (const feature of allFeatures) {
|
|
33
|
+
allFeaturesMap.set(feature.getId(), feature);
|
|
34
|
+
}
|
|
35
|
+
const dependedOnTargets = getFeaturesDependedOnTargets(targetFeatures, allFeaturesMap);
|
|
36
|
+
return Object.values(dependedOnTargets);
|
|
37
|
+
}
|
|
38
|
+
evaluateFeaturesByEvaluatedAt(features, user, mapSegmentUsers, prevUEID, evaluatedAt, userAttributesUpdated, targetTag) {
|
|
39
|
+
if (prevUEID === '') {
|
|
40
|
+
return this.evaluate(features, user, mapSegmentUsers, true, targetTag);
|
|
41
|
+
}
|
|
42
|
+
const now = Math.floor(Date.now() / 1000);
|
|
43
|
+
if (evaluatedAt < now - SECONDS_TO_RE_EVALUATE_ALL) {
|
|
44
|
+
return this.evaluate(features, user, mapSegmentUsers, true, targetTag);
|
|
45
|
+
}
|
|
46
|
+
const adjustedEvalAt = evaluatedAt - SECONDS_FOR_ADJUSTMENT;
|
|
47
|
+
const updatedFeatures = features.filter((feature) => feature.getUpdatedAt() > adjustedEvalAt ||
|
|
48
|
+
(userAttributesUpdated && feature.getRulesList().length > 0));
|
|
49
|
+
// If the UserEvaluationsID has changed, but both User Attributes and Feature Flags have not been updated,
|
|
50
|
+
// it is considered unusual and a force update should be performed.
|
|
51
|
+
if (updatedFeatures.length === 0) {
|
|
52
|
+
return this.evaluate(features, user, mapSegmentUsers, true, targetTag);
|
|
53
|
+
}
|
|
54
|
+
const evalTargets = this.getEvalFeatures(updatedFeatures, features);
|
|
55
|
+
return this.evaluate(evalTargets, user, mapSegmentUsers, false, targetTag);
|
|
56
|
+
}
|
|
57
|
+
evaluate(features, user, mapSegmentUsers, forceUpdate, targetTag) {
|
|
58
|
+
const flagVariations = {};
|
|
59
|
+
// fs need to be sorted in order from upstream to downstream.
|
|
60
|
+
const sortedFeatures = topologicalSort(features);
|
|
61
|
+
const evaluations = [];
|
|
62
|
+
const archivedIDs = [];
|
|
63
|
+
for (const feature of sortedFeatures) {
|
|
64
|
+
if (feature.getArchived()) {
|
|
65
|
+
// To keep response size small, the feature flags archived long time ago are excluded.
|
|
66
|
+
if (!this.isArchivedBeforeLastThirtyDays(feature)) {
|
|
67
|
+
archivedIDs.push(feature.getId());
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const segmentUsers = this.listSegmentIDs(feature).flatMap((id) => mapSegmentUsers.get(id) || []);
|
|
72
|
+
const [reason, variation] = this.assignUser(feature, user, segmentUsers, flagVariations);
|
|
73
|
+
// VariationId is used to check if prerequisite flag's result is what user expects it to be.
|
|
74
|
+
flagVariations[feature.getId()] = variation.getId();
|
|
75
|
+
// When the tag is set in the request,
|
|
76
|
+
// it will return only the evaluations of flags that match the tag configured on the dashboard.
|
|
77
|
+
// When empty, it will return all the evaluations of the flags in the environment.
|
|
78
|
+
if (targetTag !== '' && !this.tagExist(feature.getTagsList(), targetTag)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const evaluationID = EvaluationID(feature.getId(), feature.getVersion(), user.getId());
|
|
82
|
+
const evaluation = new evaluation_pb_1.Evaluation();
|
|
83
|
+
evaluation.setId(evaluationID);
|
|
84
|
+
evaluation.setFeatureId(feature.getId());
|
|
85
|
+
evaluation.setFeatureVersion(feature.getVersion());
|
|
86
|
+
evaluation.setUserId(user.getId());
|
|
87
|
+
evaluation.setVariationId(variation.getId());
|
|
88
|
+
evaluation.setVariationName(variation.getName());
|
|
89
|
+
evaluation.setVariationValue(variation.getValue());
|
|
90
|
+
// Deprecated
|
|
91
|
+
// FIXME: Remove the Variation when is no longer being used.
|
|
92
|
+
// For security reasons, we should remove the variation description.
|
|
93
|
+
// We copy the variation object to avoid race conditions when removing
|
|
94
|
+
// the description directly from the `variation`
|
|
95
|
+
const copyVariation = new variation_pb_1.Variation();
|
|
96
|
+
copyVariation.setId(variation.getId());
|
|
97
|
+
copyVariation.setName(variation.getName());
|
|
98
|
+
copyVariation.setValue(variation.getValue());
|
|
99
|
+
evaluation.setVariation(copyVariation);
|
|
100
|
+
evaluation.setReason(reason);
|
|
101
|
+
evaluations.push(evaluation);
|
|
102
|
+
}
|
|
103
|
+
const id = (0, userEvaluation_1.UserEvaluationsID)(user.getId(), arrayToRecord(user.getDataMap().toArray()), features);
|
|
104
|
+
const userEvaluations = (0, userEvaluation_1.NewUserEvaluations)(id, evaluations, archivedIDs, forceUpdate);
|
|
105
|
+
return userEvaluations;
|
|
106
|
+
}
|
|
107
|
+
tagExist(tags, target) {
|
|
108
|
+
return tags.includes(target);
|
|
109
|
+
}
|
|
110
|
+
/*
|
|
111
|
+
IsArchivedBeforeLastThirtyDays returns a bool value
|
|
112
|
+
indicating whether the feature flag was archived within the last thirty days.
|
|
113
|
+
*/
|
|
114
|
+
isArchivedBeforeLastThirtyDays(feature) {
|
|
115
|
+
if (!feature.getArchived()) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
const now = Math.floor(Date.now() / 1000);
|
|
119
|
+
return feature.getUpdatedAt() < now - SECONDS_TO_RE_EVALUATE_ALL;
|
|
120
|
+
}
|
|
121
|
+
listSegmentIDs(feature) {
|
|
122
|
+
const mapIDs = new Set();
|
|
123
|
+
for (const rule of feature.getRulesList()) {
|
|
124
|
+
for (const clause of rule.getClausesList()) {
|
|
125
|
+
if (clause.getOperator() === clause_pb_1.Clause.Operator.SEGMENT) {
|
|
126
|
+
clause.getValuesList().forEach((value) => mapIDs.add(value));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return Array.from(mapIDs);
|
|
131
|
+
}
|
|
132
|
+
assignUser(feature, user, segmentUsers, flagVariations) {
|
|
133
|
+
for (const pf of feature.getPrerequisitesList()) {
|
|
134
|
+
const variationId = flagVariations[pf.getFeatureId()];
|
|
135
|
+
if (!variationId) {
|
|
136
|
+
throw errors_1.EVALUATOR_ERRORS.PrerequisiteVariationNotFound;
|
|
137
|
+
}
|
|
138
|
+
if (pf.getVariationId() !== variationId) {
|
|
139
|
+
if (feature.getOffVariation() !== '') {
|
|
140
|
+
const variation = this.findVariation(feature.getOffVariation(), feature.getVariationsList());
|
|
141
|
+
const reason = (0, modelFactory_1.createReason)('', reason_pb_1.Reason.Type.PREREQUISITE);
|
|
142
|
+
return [reason, variation];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// It doesn't assign the user in case of the feature is disabled and OffVariation is not set
|
|
147
|
+
if (!feature.getEnabled() && feature.getOffVariation() !== '') {
|
|
148
|
+
const variation = this.findVariation(feature.getOffVariation(), feature.getVariationsList());
|
|
149
|
+
const reason = (0, modelFactory_1.createReason)('', reason_pb_1.Reason.Type.OFF_VARIATION);
|
|
150
|
+
return [reason, variation];
|
|
151
|
+
}
|
|
152
|
+
// evaluate from top to bottom, return if one rule matches
|
|
153
|
+
// evaluate targeting rules
|
|
154
|
+
for (const target of feature.getTargetsList()) {
|
|
155
|
+
if (target.getUsersList().includes(user.getId())) {
|
|
156
|
+
const variation = this.findVariation(target.getVariation(), feature.getVariationsList());
|
|
157
|
+
const reason = (0, modelFactory_1.createReason)('', reason_pb_1.Reason.Type.TARGET);
|
|
158
|
+
return [reason, variation];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// evaluate ruleset
|
|
162
|
+
const rule = this.ruleEvaluator.evaluate(feature.getRulesList(), user, segmentUsers, flagVariations);
|
|
163
|
+
if (rule) {
|
|
164
|
+
const strategy = rule.getStrategy();
|
|
165
|
+
if (strategy == undefined) {
|
|
166
|
+
throw errors_1.EVALUATOR_ERRORS.StrategyNotFound;
|
|
167
|
+
}
|
|
168
|
+
const variation = this.strategyEvaluator.evaluate(strategy, user.getId(), feature.getVariationsList(), feature.getId(), feature.getSamplingSeed());
|
|
169
|
+
const reason = (0, modelFactory_1.createReason)(rule.getId(), reason_pb_1.Reason.Type.RULE);
|
|
170
|
+
return [reason, variation];
|
|
171
|
+
}
|
|
172
|
+
// use default strategy
|
|
173
|
+
const defaultStrategy = feature.getDefaultStrategy();
|
|
174
|
+
if (defaultStrategy == undefined) {
|
|
175
|
+
throw errors_1.EVALUATOR_ERRORS.DefaultStrategyNotFound;
|
|
176
|
+
}
|
|
177
|
+
const variation = this.strategyEvaluator.evaluate(defaultStrategy, user.getId(), feature.getVariationsList(), feature.getId(), feature.getSamplingSeed());
|
|
178
|
+
const reason = (0, modelFactory_1.createReason)('', reason_pb_1.Reason.Type.DEFAULT);
|
|
179
|
+
return [reason, variation];
|
|
180
|
+
}
|
|
181
|
+
getEvalFeatures(targetFeatures, allFeatures) {
|
|
182
|
+
const allFeaturesMap = new Map();
|
|
183
|
+
for (const feature of allFeatures) {
|
|
184
|
+
allFeaturesMap.set(feature.getId(), feature);
|
|
185
|
+
}
|
|
186
|
+
const evals1 = getFeaturesDependedOnTargets(targetFeatures, allFeaturesMap);
|
|
187
|
+
const evals2 = getFeaturesDependsOnTargets(targetFeatures, allFeaturesMap);
|
|
188
|
+
const mergedEvals = { ...evals1, ...evals2 };
|
|
189
|
+
return Object.values(mergedEvals);
|
|
190
|
+
}
|
|
191
|
+
findVariation(v, variations) {
|
|
192
|
+
const variation = variations.find((variation) => variation.getId() === v);
|
|
193
|
+
if (!variation) {
|
|
194
|
+
throw errors_1.EVALUATOR_ERRORS.VariationNotFound;
|
|
195
|
+
}
|
|
196
|
+
return variation;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.Evaluator = Evaluator;
|
|
200
|
+
var Mark;
|
|
201
|
+
(function (Mark) {
|
|
202
|
+
Mark[Mark["Unvisited"] = 0] = "Unvisited";
|
|
203
|
+
Mark[Mark["Temporary"] = 1] = "Temporary";
|
|
204
|
+
Mark[Mark["Permanently"] = 2] = "Permanently";
|
|
205
|
+
})(Mark || (Mark = {}));
|
|
206
|
+
// FeatureIDsDependsOn returns the ids of the features that this feature depends on.
|
|
207
|
+
function featureIDsDependsOn(feature) {
|
|
208
|
+
const ids = [];
|
|
209
|
+
// Iterate over prerequisites and add their FeatureId
|
|
210
|
+
feature.getPrerequisitesList().forEach((p) => {
|
|
211
|
+
ids.push(p.getFeatureId());
|
|
212
|
+
});
|
|
213
|
+
// Iterate over rules and collect ids from clauses where the operator is FEATURE_FLAG
|
|
214
|
+
feature.getRulesList().forEach((rule) => {
|
|
215
|
+
rule.getClausesList().forEach((clause) => {
|
|
216
|
+
if (clause.getOperator() === clause_pb_1.Clause.Operator.FEATURE_FLAG) {
|
|
217
|
+
ids.push(clause.getAttribute());
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
return ids;
|
|
222
|
+
}
|
|
223
|
+
// Error types
|
|
224
|
+
class ErrCycleExists extends Error {
|
|
225
|
+
constructor() {
|
|
226
|
+
super('Cycle exists in the graph');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
class ErrFeatureNotFound extends Error {
|
|
230
|
+
constructor() {
|
|
231
|
+
super('Feature not found');
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Topological sort function
|
|
235
|
+
function topologicalSort(features) {
|
|
236
|
+
const marks = {};
|
|
237
|
+
const mapFeatures = {};
|
|
238
|
+
features.forEach((f) => {
|
|
239
|
+
marks[f.getId()] = Mark.Unvisited;
|
|
240
|
+
mapFeatures[f.getId()] = f;
|
|
241
|
+
});
|
|
242
|
+
const sortedFeatures = [];
|
|
243
|
+
const sort = (f) => {
|
|
244
|
+
const fId = f.getId();
|
|
245
|
+
if (marks[fId] === Mark.Permanently)
|
|
246
|
+
return;
|
|
247
|
+
if (marks[fId] === Mark.Temporary) {
|
|
248
|
+
throw new ErrCycleExists();
|
|
249
|
+
}
|
|
250
|
+
marks[fId] = Mark.Temporary;
|
|
251
|
+
const dependentFeatureIds = featureIDsDependsOn(f);
|
|
252
|
+
for (const fid of dependentFeatureIds) {
|
|
253
|
+
const pf = mapFeatures[fid];
|
|
254
|
+
if (!pf) {
|
|
255
|
+
throw new ErrFeatureNotFound();
|
|
256
|
+
}
|
|
257
|
+
sort(pf);
|
|
258
|
+
}
|
|
259
|
+
marks[fId] = Mark.Permanently;
|
|
260
|
+
sortedFeatures.push(f);
|
|
261
|
+
};
|
|
262
|
+
// Process each feature
|
|
263
|
+
for (const f of features) {
|
|
264
|
+
if (marks[f.getId()] === Mark.Unvisited) {
|
|
265
|
+
sort(f);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return sortedFeatures;
|
|
269
|
+
}
|
|
270
|
+
// getFeaturesDependedOnTargets returns the features that are depended on the target features.
|
|
271
|
+
// targetFeatures are included in the result.
|
|
272
|
+
function getFeaturesDependedOnTargets(targets, all) {
|
|
273
|
+
const evals = {};
|
|
274
|
+
const dfs = (f) => {
|
|
275
|
+
// If the feature is already in the evals map, skip
|
|
276
|
+
if (evals[f.getId()])
|
|
277
|
+
return;
|
|
278
|
+
// Add feature to evals
|
|
279
|
+
evals[f.getId()] = f;
|
|
280
|
+
// Get dependent features recursively
|
|
281
|
+
const featureDependencies = featureIDsDependsOn(f);
|
|
282
|
+
featureDependencies.forEach((fid) => {
|
|
283
|
+
const target = all.get(fid);
|
|
284
|
+
if (target !== undefined) {
|
|
285
|
+
dfs(target);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
// Start DFS for each target feature
|
|
290
|
+
targets.forEach((f) => dfs(f));
|
|
291
|
+
return evals;
|
|
292
|
+
}
|
|
293
|
+
// getFeaturesDependsOnTargets returns the features that depend on the target features.
|
|
294
|
+
// targetFeatures are included in the result.
|
|
295
|
+
function getFeaturesDependsOnTargets(targets, all) {
|
|
296
|
+
const evals = {};
|
|
297
|
+
// Mark target features in the evals map initially
|
|
298
|
+
targets.forEach((f) => {
|
|
299
|
+
evals[f.getId()] = f;
|
|
300
|
+
});
|
|
301
|
+
// Depth-first search to determine if a feature depends on a target feature
|
|
302
|
+
const dfs = (f) => {
|
|
303
|
+
if (evals[f.getId()]) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
const featureDependencies = featureIDsDependsOn(f);
|
|
307
|
+
for (const fid of featureDependencies) {
|
|
308
|
+
const dependentFeature = all.get(fid);
|
|
309
|
+
if (dependentFeature && dfs(dependentFeature)) {
|
|
310
|
+
// If the feature depends on one of the target features, add it to evals
|
|
311
|
+
evals[f.getId()] = f;
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return false;
|
|
316
|
+
};
|
|
317
|
+
// Apply DFS for all features, except target features
|
|
318
|
+
all.forEach((f) => {
|
|
319
|
+
dfs(f);
|
|
320
|
+
});
|
|
321
|
+
return evals;
|
|
322
|
+
}
|
|
323
|
+
function arrayToRecord(arr) {
|
|
324
|
+
return arr.reduce((acc, [key, value]) => {
|
|
325
|
+
acc[key] = value;
|
|
326
|
+
return acc;
|
|
327
|
+
}, {});
|
|
328
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// source: google/api/annotations.proto
|
|
4
|
+
/**
|
|
5
|
+
* @fileoverview
|
|
6
|
+
* @enhanceable
|
|
7
|
+
* @suppress {missingRequire} reports error on implicit type usages.
|
|
8
|
+
* @suppress {messageConventions} JS Compiler reports an error if a variable or
|
|
9
|
+
* field starts with 'MSG_' and isn't a translatable message.
|
|
10
|
+
* @public
|
|
11
|
+
*/
|
|
12
|
+
// GENERATED CODE -- DO NOT EDIT!
|
|
13
|
+
|
|
14
|
+
// @ts-nocheck
|
|
15
|
+
|
|
16
|
+
var jspb = require('google-protobuf');
|
|
17
|
+
var goog = jspb;
|
|
18
|
+
var global = typeof globalThis !== 'undefined' && globalThis || typeof window !== 'undefined' && window || typeof global !== 'undefined' && global || typeof self !== 'undefined' && self || function () {
|
|
19
|
+
return this;
|
|
20
|
+
}.call(null) || Function('return this')();
|
|
21
|
+
var google_api_http_pb = require('../../google/api/http_pb.js');
|
|
22
|
+
goog.object.extend(proto, google_api_http_pb);
|
|
23
|
+
var google_protobuf_descriptor_pb = require('google-protobuf/google/protobuf/descriptor_pb.js');
|
|
24
|
+
goog.object.extend(proto, google_protobuf_descriptor_pb);
|
|
25
|
+
goog.exportSymbol('proto.google.api.http', null, global);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A tuple of {field number, class constructor} for the extension
|
|
29
|
+
* field named `http`.
|
|
30
|
+
* @type {!jspb.ExtensionFieldInfo<!proto.google.api.HttpRule>}
|
|
31
|
+
*/
|
|
32
|
+
proto.google.api.http = new jspb.ExtensionFieldInfo(72295728, {
|
|
33
|
+
http: 0
|
|
34
|
+
}, google_api_http_pb.HttpRule, /** @type {?function((boolean|undefined),!jspb.Message=): !Object} */
|
|
35
|
+
google_api_http_pb.HttpRule.toObject, 0);
|
|
36
|
+
google_protobuf_descriptor_pb.MethodOptions.extensionsBinary[72295728] = new jspb.ExtensionFieldBinaryInfo(proto.google.api.http, jspb.BinaryReader.prototype.readMessage, jspb.BinaryWriter.prototype.writeMessage, google_api_http_pb.HttpRule.serializeBinaryToWriter, google_api_http_pb.HttpRule.deserializeBinaryFromReader, false);
|
|
37
|
+
// This registers the extension field with the extended class, so that
|
|
38
|
+
// toObject() will function correctly.
|
|
39
|
+
google_protobuf_descriptor_pb.MethodOptions.extensions[72295728] = proto.google.api.http;
|
|
40
|
+
goog.object.extend(exports, proto.google.api);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// source: google/api/annotations.proto
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview
|
|
4
|
+
* @enhanceable
|
|
5
|
+
* @suppress {missingRequire} reports error on implicit type usages.
|
|
6
|
+
* @suppress {messageConventions} JS Compiler reports an error if a variable or
|
|
7
|
+
* field starts with 'MSG_' and isn't a translatable message.
|
|
8
|
+
* @public
|
|
9
|
+
*/
|
|
10
|
+
// GENERATED CODE -- DO NOT EDIT!
|
|
11
|
+
|
|
12
|
+
// @ts-nocheck
|
|
13
|
+
|
|
14
|
+
var jspb = require('google-protobuf');
|
|
15
|
+
var goog = jspb;
|
|
16
|
+
var global =
|
|
17
|
+
(typeof globalThis !== 'undefined' && globalThis) ||
|
|
18
|
+
(typeof window !== 'undefined' && window) ||
|
|
19
|
+
(typeof global !== 'undefined' && global) ||
|
|
20
|
+
(typeof self !== 'undefined' && self) ||
|
|
21
|
+
(function () { return this; }).call(null) ||
|
|
22
|
+
Function('return this')();
|
|
23
|
+
|
|
24
|
+
var google_api_http_pb = require('../../google/api/http_pb.js');
|
|
25
|
+
goog.object.extend(proto, google_api_http_pb);
|
|
26
|
+
var google_protobuf_descriptor_pb = require('google-protobuf/google/protobuf/descriptor_pb.js');
|
|
27
|
+
goog.object.extend(proto, google_protobuf_descriptor_pb);
|
|
28
|
+
goog.exportSymbol('proto.google.api.http', null, global);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A tuple of {field number, class constructor} for the extension
|
|
32
|
+
* field named `http`.
|
|
33
|
+
* @type {!jspb.ExtensionFieldInfo<!proto.google.api.HttpRule>}
|
|
34
|
+
*/
|
|
35
|
+
proto.google.api.http = new jspb.ExtensionFieldInfo(
|
|
36
|
+
72295728,
|
|
37
|
+
{http: 0},
|
|
38
|
+
google_api_http_pb.HttpRule,
|
|
39
|
+
/** @type {?function((boolean|undefined),!jspb.Message=): !Object} */ (
|
|
40
|
+
google_api_http_pb.HttpRule.toObject),
|
|
41
|
+
0);
|
|
42
|
+
|
|
43
|
+
google_protobuf_descriptor_pb.MethodOptions.extensionsBinary[72295728] = new jspb.ExtensionFieldBinaryInfo(
|
|
44
|
+
proto.google.api.http,
|
|
45
|
+
jspb.BinaryReader.prototype.readMessage,
|
|
46
|
+
jspb.BinaryWriter.prototype.writeMessage,
|
|
47
|
+
google_api_http_pb.HttpRule.serializeBinaryToWriter,
|
|
48
|
+
google_api_http_pb.HttpRule.deserializeBinaryFromReader,
|
|
49
|
+
false);
|
|
50
|
+
// This registers the extension field with the extended class, so that
|
|
51
|
+
// toObject() will function correctly.
|
|
52
|
+
google_protobuf_descriptor_pb.MethodOptions.extensions[72295728] = proto.google.api.http;
|
|
53
|
+
|
|
54
|
+
goog.object.extend(exports, proto.google.api);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// package: google.api
|
|
2
|
+
// file: google/api/http.proto
|
|
3
|
+
|
|
4
|
+
import * as jspb from 'google-protobuf';
|
|
5
|
+
|
|
6
|
+
export class Http extends jspb.Message {
|
|
7
|
+
clearRulesList(): void;
|
|
8
|
+
getRulesList(): Array<HttpRule>;
|
|
9
|
+
setRulesList(value: Array<HttpRule>): void;
|
|
10
|
+
addRules(value?: HttpRule, index?: number): HttpRule;
|
|
11
|
+
|
|
12
|
+
getFullyDecodeReservedExpansion(): boolean;
|
|
13
|
+
setFullyDecodeReservedExpansion(value: boolean): void;
|
|
14
|
+
|
|
15
|
+
serializeBinary(): Uint8Array;
|
|
16
|
+
toObject(includeInstance?: boolean): Http.AsObject;
|
|
17
|
+
static toObject(includeInstance: boolean, msg: Http): Http.AsObject;
|
|
18
|
+
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
|
19
|
+
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
|
20
|
+
static serializeBinaryToWriter(message: Http, writer: jspb.BinaryWriter): void;
|
|
21
|
+
static deserializeBinary(bytes: Uint8Array): Http;
|
|
22
|
+
static deserializeBinaryFromReader(message: Http, reader: jspb.BinaryReader): Http;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export namespace Http {
|
|
26
|
+
export type AsObject = {
|
|
27
|
+
rulesList: Array<HttpRule.AsObject>,
|
|
28
|
+
fullyDecodeReservedExpansion: boolean,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class HttpRule extends jspb.Message {
|
|
33
|
+
getSelector(): string;
|
|
34
|
+
setSelector(value: string): void;
|
|
35
|
+
|
|
36
|
+
hasGet(): boolean;
|
|
37
|
+
clearGet(): void;
|
|
38
|
+
getGet(): string;
|
|
39
|
+
setGet(value: string): void;
|
|
40
|
+
|
|
41
|
+
hasPut(): boolean;
|
|
42
|
+
clearPut(): void;
|
|
43
|
+
getPut(): string;
|
|
44
|
+
setPut(value: string): void;
|
|
45
|
+
|
|
46
|
+
hasPost(): boolean;
|
|
47
|
+
clearPost(): void;
|
|
48
|
+
getPost(): string;
|
|
49
|
+
setPost(value: string): void;
|
|
50
|
+
|
|
51
|
+
hasDelete(): boolean;
|
|
52
|
+
clearDelete(): void;
|
|
53
|
+
getDelete(): string;
|
|
54
|
+
setDelete(value: string): void;
|
|
55
|
+
|
|
56
|
+
hasPatch(): boolean;
|
|
57
|
+
clearPatch(): void;
|
|
58
|
+
getPatch(): string;
|
|
59
|
+
setPatch(value: string): void;
|
|
60
|
+
|
|
61
|
+
hasCustom(): boolean;
|
|
62
|
+
clearCustom(): void;
|
|
63
|
+
getCustom(): CustomHttpPattern | undefined;
|
|
64
|
+
setCustom(value?: CustomHttpPattern): void;
|
|
65
|
+
|
|
66
|
+
getBody(): string;
|
|
67
|
+
setBody(value: string): void;
|
|
68
|
+
|
|
69
|
+
clearAdditionalBindingsList(): void;
|
|
70
|
+
getAdditionalBindingsList(): Array<HttpRule>;
|
|
71
|
+
setAdditionalBindingsList(value: Array<HttpRule>): void;
|
|
72
|
+
addAdditionalBindings(value?: HttpRule, index?: number): HttpRule;
|
|
73
|
+
|
|
74
|
+
getPatternCase(): HttpRule.PatternCase;
|
|
75
|
+
serializeBinary(): Uint8Array;
|
|
76
|
+
toObject(includeInstance?: boolean): HttpRule.AsObject;
|
|
77
|
+
static toObject(includeInstance: boolean, msg: HttpRule): HttpRule.AsObject;
|
|
78
|
+
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
|
79
|
+
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
|
80
|
+
static serializeBinaryToWriter(message: HttpRule, writer: jspb.BinaryWriter): void;
|
|
81
|
+
static deserializeBinary(bytes: Uint8Array): HttpRule;
|
|
82
|
+
static deserializeBinaryFromReader(message: HttpRule, reader: jspb.BinaryReader): HttpRule;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export namespace HttpRule {
|
|
86
|
+
export type AsObject = {
|
|
87
|
+
selector: string,
|
|
88
|
+
get: string,
|
|
89
|
+
put: string,
|
|
90
|
+
post: string,
|
|
91
|
+
pb_delete: string,
|
|
92
|
+
patch: string,
|
|
93
|
+
custom?: CustomHttpPattern.AsObject,
|
|
94
|
+
body: string,
|
|
95
|
+
additionalBindingsList: Array<HttpRule.AsObject>,
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export enum PatternCase {
|
|
99
|
+
PATTERN_NOT_SET = 0,
|
|
100
|
+
GET = 2,
|
|
101
|
+
PUT = 3,
|
|
102
|
+
POST = 4,
|
|
103
|
+
DELETE = 5,
|
|
104
|
+
PATCH = 6,
|
|
105
|
+
CUSTOM = 8,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class CustomHttpPattern extends jspb.Message {
|
|
110
|
+
getKind(): string;
|
|
111
|
+
setKind(value: string): void;
|
|
112
|
+
|
|
113
|
+
getPath(): string;
|
|
114
|
+
setPath(value: string): void;
|
|
115
|
+
|
|
116
|
+
serializeBinary(): Uint8Array;
|
|
117
|
+
toObject(includeInstance?: boolean): CustomHttpPattern.AsObject;
|
|
118
|
+
static toObject(includeInstance: boolean, msg: CustomHttpPattern): CustomHttpPattern.AsObject;
|
|
119
|
+
static extensions: {[key: number]: jspb.ExtensionFieldInfo<jspb.Message>};
|
|
120
|
+
static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo<jspb.Message>};
|
|
121
|
+
static serializeBinaryToWriter(message: CustomHttpPattern, writer: jspb.BinaryWriter): void;
|
|
122
|
+
static deserializeBinary(bytes: Uint8Array): CustomHttpPattern;
|
|
123
|
+
static deserializeBinaryFromReader(message: CustomHttpPattern, reader: jspb.BinaryReader): CustomHttpPattern;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export namespace CustomHttpPattern {
|
|
127
|
+
export type AsObject = {
|
|
128
|
+
kind: string,
|
|
129
|
+
path: string,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|