@microsoft/feature-management 2.1.0 → 2.2.0
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/dist/commonjs/featureManager.js +5 -5
- package/dist/commonjs/featureManager.js.map +1 -1
- package/dist/commonjs/featureProvider.js.map +1 -1
- package/dist/commonjs/filter/recurrence/evaluator.js +142 -0
- package/dist/commonjs/filter/recurrence/evaluator.js.map +1 -0
- package/dist/commonjs/filter/recurrence/model.js +52 -0
- package/dist/commonjs/filter/recurrence/model.js.map +1 -0
- package/dist/commonjs/filter/recurrence/utils.js +53 -0
- package/dist/commonjs/filter/recurrence/utils.js.map +1 -0
- package/dist/commonjs/filter/recurrence/validator.js +197 -0
- package/dist/commonjs/filter/recurrence/validator.js.map +1 -0
- package/dist/commonjs/filter/{TargetingFilter.js → targetingFilter.js} +1 -1
- package/dist/commonjs/filter/{TargetingFilter.js.map → targetingFilter.js.map} +1 -1
- package/dist/commonjs/filter/timeWindowFilter.js +49 -0
- package/dist/commonjs/filter/timeWindowFilter.js.map +1 -0
- package/dist/commonjs/filter/utils.js +16 -0
- package/dist/commonjs/filter/utils.js.map +1 -0
- package/dist/commonjs/variant/{Variant.js → variant.js} +1 -1
- package/dist/commonjs/variant/{Variant.js.map → variant.js.map} +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/commonjs/version.js.map +1 -1
- package/dist/esm/featureManager.js +3 -3
- package/dist/esm/featureManager.js.map +1 -1
- package/dist/esm/featureProvider.js.map +1 -1
- package/dist/esm/filter/recurrence/evaluator.js +140 -0
- package/dist/esm/filter/recurrence/evaluator.js.map +1 -0
- package/dist/esm/filter/recurrence/model.js +49 -0
- package/dist/esm/filter/recurrence/model.js.map +1 -0
- package/dist/esm/filter/recurrence/utils.js +48 -0
- package/dist/esm/filter/recurrence/utils.js.map +1 -0
- package/dist/esm/filter/recurrence/validator.js +184 -0
- package/dist/esm/filter/recurrence/validator.js.map +1 -0
- package/dist/esm/filter/{TargetingFilter.js → targetingFilter.js} +1 -1
- package/dist/esm/filter/{TargetingFilter.js.map → targetingFilter.js.map} +1 -1
- package/dist/esm/filter/timeWindowFilter.js +47 -0
- package/dist/esm/filter/timeWindowFilter.js.map +1 -0
- package/dist/esm/filter/utils.js +11 -0
- package/dist/esm/filter/utils.js.map +1 -0
- package/dist/esm/variant/{Variant.js → variant.js} +1 -1
- package/dist/esm/variant/{Variant.js.map → variant.js.map} +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/umd/index.js +439 -3
- package/dist/umd/index.js.map +1 -1
- package/package.json +3 -2
- package/types/index.d.ts +42 -42
- package/dist/commonjs/filter/TimeWindowFilter.js +0 -22
- package/dist/commonjs/filter/TimeWindowFilter.js.map +0 -1
- package/dist/esm/filter/TimeWindowFilter.js +0 -20
- package/dist/esm/filter/TimeWindowFilter.js.map +0 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var utils = require('../utils.js');
|
|
4
|
+
var model = require('./model.js');
|
|
5
|
+
var utils$1 = require('./utils.js');
|
|
6
|
+
|
|
7
|
+
// Copyright (c) Microsoft Corporation.
|
|
8
|
+
// Licensed under the MIT license.
|
|
9
|
+
const START_NOT_MATCHED_ERROR_MESSAGE = "Start date is not a valid first occurrence.";
|
|
10
|
+
const TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE = "Time window duration cannot be longer than how frequently it occurs or be longer than 10 years.";
|
|
11
|
+
const PATTERN = "Recurrence.Pattern";
|
|
12
|
+
const PATTERN_TYPE = "Recurrence.Pattern.Type";
|
|
13
|
+
const INTERVAL = "Recurrence.Pattern.Interval";
|
|
14
|
+
const DAYS_OF_WEEK = "Recurrence.Pattern.DaysOfWeek";
|
|
15
|
+
const FIRST_DAY_OF_WEEK = "Recurrence.Pattern.FirstDayOfWeek";
|
|
16
|
+
const RANGE = "Recurrence.Range";
|
|
17
|
+
const RANGE_TYPE = "Recurrence.Range.Type";
|
|
18
|
+
const END_DATE = "Recurrence.Range.EndDate";
|
|
19
|
+
const NUMBER_OF_OCCURRENCES = "Recurrence.Range.NumberOfOccurrences";
|
|
20
|
+
/**
|
|
21
|
+
* Parses @see RecurrenceParameters into a @see RecurrenceSpec object. If the parameter is invalid, an error will be thrown.
|
|
22
|
+
* @param startTime The start time of the base time window
|
|
23
|
+
* @param day2 The end time of the base time window
|
|
24
|
+
* @param recurrenceParameters The @see RecurrenceParameters to parse
|
|
25
|
+
* @param TimeZoneOffset The time zone offset in milliseconds, by default 0
|
|
26
|
+
* @returns A @see RecurrenceSpec object
|
|
27
|
+
*/
|
|
28
|
+
function parseRecurrenceParameter(startTime, endTime, recurrenceParameters, TimeZoneOffset = 0) {
|
|
29
|
+
if (startTime === undefined) {
|
|
30
|
+
throw new Error(utils.buildInvalidParameterErrorMessage("Start", utils.REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));
|
|
31
|
+
}
|
|
32
|
+
if (endTime === undefined) {
|
|
33
|
+
throw new Error(utils.buildInvalidParameterErrorMessage("End", utils.REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));
|
|
34
|
+
}
|
|
35
|
+
if (startTime >= endTime) {
|
|
36
|
+
throw new Error(utils.buildInvalidParameterErrorMessage("End", utils.VALUE_OUT_OF_RANGE_ERROR_MESSAGE));
|
|
37
|
+
}
|
|
38
|
+
const timeWindowDuration = endTime.getTime() - startTime.getTime();
|
|
39
|
+
if (timeWindowDuration > 10 * 365 * model.ONE_DAY_IN_MILLISECONDS) { // time window duration cannot be longer than 10 years
|
|
40
|
+
throw new Error(utils.buildInvalidParameterErrorMessage("End", TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE));
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
startTime: startTime,
|
|
44
|
+
duration: timeWindowDuration,
|
|
45
|
+
pattern: parseRecurrencePattern(startTime, endTime, recurrenceParameters, TimeZoneOffset),
|
|
46
|
+
range: parseRecurrenceRange(startTime, recurrenceParameters),
|
|
47
|
+
timezoneOffset: TimeZoneOffset
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function parseRecurrencePattern(startTime, endTime, recurrenceParameters, timeZoneOffset) {
|
|
51
|
+
const rawPattern = recurrenceParameters.Pattern;
|
|
52
|
+
if (rawPattern === undefined) {
|
|
53
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(PATTERN, utils.REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));
|
|
54
|
+
}
|
|
55
|
+
if (rawPattern.Type === undefined) {
|
|
56
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(PATTERN_TYPE, utils.REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));
|
|
57
|
+
}
|
|
58
|
+
const patternType = model.RecurrencePatternType[rawPattern.Type];
|
|
59
|
+
if (patternType === undefined) {
|
|
60
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(PATTERN_TYPE, utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
61
|
+
}
|
|
62
|
+
let interval = rawPattern.Interval;
|
|
63
|
+
if (interval !== undefined) {
|
|
64
|
+
if (typeof interval !== "number") {
|
|
65
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(INTERVAL, utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
66
|
+
}
|
|
67
|
+
else if (interval <= 0 || !Number.isInteger(interval)) {
|
|
68
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(INTERVAL, utils.VALUE_OUT_OF_RANGE_ERROR_MESSAGE));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
interval = 1;
|
|
73
|
+
}
|
|
74
|
+
const parsedPattern = {
|
|
75
|
+
type: patternType,
|
|
76
|
+
interval: interval
|
|
77
|
+
};
|
|
78
|
+
const timeWindowDuration = endTime.getTime() - startTime.getTime();
|
|
79
|
+
if (patternType === model.RecurrencePatternType.Daily) {
|
|
80
|
+
if (timeWindowDuration > interval * model.ONE_DAY_IN_MILLISECONDS) {
|
|
81
|
+
throw new Error(utils.buildInvalidParameterErrorMessage("End", TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (patternType === model.RecurrencePatternType.Weekly) {
|
|
85
|
+
let firstDayOfWeek;
|
|
86
|
+
if (rawPattern.FirstDayOfWeek !== undefined) {
|
|
87
|
+
firstDayOfWeek = model.DayOfWeek[rawPattern.FirstDayOfWeek];
|
|
88
|
+
if (firstDayOfWeek === undefined) {
|
|
89
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(FIRST_DAY_OF_WEEK, utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
firstDayOfWeek = model.DayOfWeek.Sunday;
|
|
94
|
+
}
|
|
95
|
+
parsedPattern.firstDayOfWeek = firstDayOfWeek;
|
|
96
|
+
if (rawPattern.DaysOfWeek === undefined || rawPattern.DaysOfWeek.length === 0) {
|
|
97
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(DAYS_OF_WEEK, utils.REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));
|
|
98
|
+
}
|
|
99
|
+
if (!Array.isArray(rawPattern.DaysOfWeek)) {
|
|
100
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(DAYS_OF_WEEK, utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
101
|
+
}
|
|
102
|
+
const daysOfWeek = [...new Set(rawPattern.DaysOfWeek.map(day => model.DayOfWeek[day]))]; // dedup array
|
|
103
|
+
if (daysOfWeek.some(day => day === undefined)) {
|
|
104
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(DAYS_OF_WEEK, utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
105
|
+
}
|
|
106
|
+
if (timeWindowDuration > interval * model.DAYS_PER_WEEK * model.ONE_DAY_IN_MILLISECONDS ||
|
|
107
|
+
!isDurationCompliantWithDaysOfWeek(timeWindowDuration, interval, daysOfWeek, firstDayOfWeek)) {
|
|
108
|
+
throw new Error(utils.buildInvalidParameterErrorMessage("End", TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE));
|
|
109
|
+
}
|
|
110
|
+
parsedPattern.daysOfWeek = daysOfWeek;
|
|
111
|
+
// check whether "Start" is a valid first occurrence
|
|
112
|
+
const alignedStartDay = utils$1.getDayOfWeek(startTime, timeZoneOffset);
|
|
113
|
+
if (!daysOfWeek.find(day => day === alignedStartDay)) {
|
|
114
|
+
throw new Error(utils.buildInvalidParameterErrorMessage("Start", START_NOT_MATCHED_ERROR_MESSAGE));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return parsedPattern;
|
|
118
|
+
}
|
|
119
|
+
function parseRecurrenceRange(startTime, recurrenceParameters) {
|
|
120
|
+
const rawRange = recurrenceParameters.Range;
|
|
121
|
+
if (rawRange === undefined) {
|
|
122
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(RANGE, utils.REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));
|
|
123
|
+
}
|
|
124
|
+
if (rawRange.Type === undefined) {
|
|
125
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(RANGE_TYPE, utils.REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));
|
|
126
|
+
}
|
|
127
|
+
const rangeType = model.RecurrenceRangeType[rawRange.Type];
|
|
128
|
+
if (rangeType === undefined) {
|
|
129
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(RANGE_TYPE, utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
130
|
+
}
|
|
131
|
+
const parsedRange = { type: rangeType };
|
|
132
|
+
if (rangeType === model.RecurrenceRangeType.EndDate) {
|
|
133
|
+
let endDate;
|
|
134
|
+
if (rawRange.EndDate !== undefined) {
|
|
135
|
+
endDate = new Date(rawRange.EndDate);
|
|
136
|
+
if (isNaN(endDate.getTime())) {
|
|
137
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(END_DATE, utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
138
|
+
}
|
|
139
|
+
if (endDate < startTime) {
|
|
140
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(END_DATE, utils.VALUE_OUT_OF_RANGE_ERROR_MESSAGE));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
endDate = new Date(8.64e15); // the maximum date in ECMAScript: https://262.ecma-international.org/5.1/#sec-15.9.1.1
|
|
145
|
+
}
|
|
146
|
+
parsedRange.endDate = endDate;
|
|
147
|
+
}
|
|
148
|
+
else if (rangeType === model.RecurrenceRangeType.Numbered) {
|
|
149
|
+
let numberOfOccurrences = rawRange.NumberOfOccurrences;
|
|
150
|
+
if (numberOfOccurrences !== undefined) {
|
|
151
|
+
if (typeof numberOfOccurrences !== "number") {
|
|
152
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(NUMBER_OF_OCCURRENCES, utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
153
|
+
}
|
|
154
|
+
else if (numberOfOccurrences <= 0 || !Number.isInteger(numberOfOccurrences)) {
|
|
155
|
+
throw new Error(utils.buildInvalidParameterErrorMessage(NUMBER_OF_OCCURRENCES, utils.VALUE_OUT_OF_RANGE_ERROR_MESSAGE));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
numberOfOccurrences = Number.MAX_SAFE_INTEGER;
|
|
160
|
+
}
|
|
161
|
+
parsedRange.numberOfOccurrences = numberOfOccurrences;
|
|
162
|
+
}
|
|
163
|
+
return parsedRange;
|
|
164
|
+
}
|
|
165
|
+
function isDurationCompliantWithDaysOfWeek(duration, interval, daysOfWeek, firstDayOfWeek) {
|
|
166
|
+
if (daysOfWeek.length === 1) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
const sortedDaysOfWeek = utils$1.sortDaysOfWeek(daysOfWeek, firstDayOfWeek);
|
|
170
|
+
let prev = sortedDaysOfWeek[0]; // the closest occurrence day to the first day of week
|
|
171
|
+
let minGap = model.DAYS_PER_WEEK * model.ONE_DAY_IN_MILLISECONDS;
|
|
172
|
+
for (let i = 1; i < sortedDaysOfWeek.length; i++) { // skip the first day
|
|
173
|
+
const gap = utils$1.calculateWeeklyDayOffset(sortedDaysOfWeek[i], prev) * model.ONE_DAY_IN_MILLISECONDS;
|
|
174
|
+
minGap = gap < minGap ? gap : minGap;
|
|
175
|
+
prev = sortedDaysOfWeek[i];
|
|
176
|
+
}
|
|
177
|
+
// It may across weeks. Check the next week if the interval is one week.
|
|
178
|
+
if (interval == 1) {
|
|
179
|
+
const gap = utils$1.calculateWeeklyDayOffset(sortedDaysOfWeek[0], prev) * model.ONE_DAY_IN_MILLISECONDS;
|
|
180
|
+
minGap = gap < minGap ? gap : minGap;
|
|
181
|
+
}
|
|
182
|
+
return minGap >= duration;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
exports.DAYS_OF_WEEK = DAYS_OF_WEEK;
|
|
186
|
+
exports.END_DATE = END_DATE;
|
|
187
|
+
exports.FIRST_DAY_OF_WEEK = FIRST_DAY_OF_WEEK;
|
|
188
|
+
exports.INTERVAL = INTERVAL;
|
|
189
|
+
exports.NUMBER_OF_OCCURRENCES = NUMBER_OF_OCCURRENCES;
|
|
190
|
+
exports.PATTERN = PATTERN;
|
|
191
|
+
exports.PATTERN_TYPE = PATTERN_TYPE;
|
|
192
|
+
exports.RANGE = RANGE;
|
|
193
|
+
exports.RANGE_TYPE = RANGE_TYPE;
|
|
194
|
+
exports.START_NOT_MATCHED_ERROR_MESSAGE = START_NOT_MATCHED_ERROR_MESSAGE;
|
|
195
|
+
exports.TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE = TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE;
|
|
196
|
+
exports.parseRecurrenceParameter = parseRecurrenceParameter;
|
|
197
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sources":["../../../../src/filter/recurrence/validator.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { RecurrenceParameters } from \"../timeWindowFilter.js\";\nimport { VALUE_OUT_OF_RANGE_ERROR_MESSAGE, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE, REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE, buildInvalidParameterErrorMessage } from \"../utils.js\";\nimport { DayOfWeek, RecurrenceSpec, RecurrencePattern, RecurrenceRange, RecurrencePatternType, RecurrenceRangeType, DAYS_PER_WEEK, ONE_DAY_IN_MILLISECONDS } from \"./model.js\";\nimport { calculateWeeklyDayOffset, sortDaysOfWeek, getDayOfWeek } from \"./utils.js\";\n\nexport const START_NOT_MATCHED_ERROR_MESSAGE = \"Start date is not a valid first occurrence.\";\nexport const TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE = \"Time window duration cannot be longer than how frequently it occurs or be longer than 10 years.\";\nexport const PATTERN = \"Recurrence.Pattern\";\nexport const PATTERN_TYPE = \"Recurrence.Pattern.Type\";\nexport const INTERVAL = \"Recurrence.Pattern.Interval\";\nexport const DAYS_OF_WEEK = \"Recurrence.Pattern.DaysOfWeek\";\nexport const FIRST_DAY_OF_WEEK = \"Recurrence.Pattern.FirstDayOfWeek\";\nexport const RANGE = \"Recurrence.Range\";\nexport const RANGE_TYPE = \"Recurrence.Range.Type\";\nexport const END_DATE = \"Recurrence.Range.EndDate\";\nexport const NUMBER_OF_OCCURRENCES = \"Recurrence.Range.NumberOfOccurrences\";\n\n/**\n * Parses @see RecurrenceParameters into a @see RecurrenceSpec object. If the parameter is invalid, an error will be thrown.\n * @param startTime The start time of the base time window\n * @param day2 The end time of the base time window\n * @param recurrenceParameters The @see RecurrenceParameters to parse\n * @param TimeZoneOffset The time zone offset in milliseconds, by default 0\n * @returns A @see RecurrenceSpec object\n */\nexport function parseRecurrenceParameter(startTime: Date | undefined, endTime: Date | undefined, recurrenceParameters: RecurrenceParameters, TimeZoneOffset: number = 0): RecurrenceSpec {\n if (startTime === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(\"Start\", REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));\n }\n if (endTime === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(\"End\", REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));\n }\n if (startTime >= endTime) {\n throw new Error(buildInvalidParameterErrorMessage(\"End\", VALUE_OUT_OF_RANGE_ERROR_MESSAGE));\n }\n const timeWindowDuration = endTime.getTime() - startTime.getTime();\n if (timeWindowDuration > 10 * 365 * ONE_DAY_IN_MILLISECONDS) { // time window duration cannot be longer than 10 years\n throw new Error(buildInvalidParameterErrorMessage(\"End\", TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE));\n }\n\n return {\n startTime: startTime,\n duration: timeWindowDuration,\n pattern: parseRecurrencePattern(startTime, endTime, recurrenceParameters, TimeZoneOffset),\n range: parseRecurrenceRange(startTime, recurrenceParameters),\n timezoneOffset: TimeZoneOffset\n };\n}\n\nfunction parseRecurrencePattern(startTime: Date, endTime: Date, recurrenceParameters: RecurrenceParameters, timeZoneOffset: number): RecurrencePattern {\n const rawPattern = recurrenceParameters.Pattern;\n if (rawPattern === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(PATTERN, REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));\n }\n if (rawPattern.Type === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(PATTERN_TYPE, REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));\n }\n const patternType = RecurrencePatternType[rawPattern.Type];\n if (patternType === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(PATTERN_TYPE, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n }\n let interval = rawPattern.Interval;\n if (interval !== undefined) {\n if (typeof interval !== \"number\") {\n throw new Error(buildInvalidParameterErrorMessage(INTERVAL, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n } else if (interval <= 0 || !Number.isInteger(interval)) {\n throw new Error(buildInvalidParameterErrorMessage(INTERVAL, VALUE_OUT_OF_RANGE_ERROR_MESSAGE));\n }\n } else {\n interval = 1;\n }\n const parsedPattern: RecurrencePattern = {\n type: patternType,\n interval: interval\n };\n const timeWindowDuration = endTime.getTime() - startTime.getTime();\n if (patternType === RecurrencePatternType.Daily) {\n if (timeWindowDuration > interval * ONE_DAY_IN_MILLISECONDS) {\n throw new Error(buildInvalidParameterErrorMessage(\"End\", TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE));\n }\n } else if (patternType === RecurrencePatternType.Weekly) {\n let firstDayOfWeek: DayOfWeek;\n if (rawPattern.FirstDayOfWeek !== undefined) {\n firstDayOfWeek = DayOfWeek[rawPattern.FirstDayOfWeek];\n if (firstDayOfWeek === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(FIRST_DAY_OF_WEEK, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n }\n }\n else {\n firstDayOfWeek = DayOfWeek.Sunday;\n }\n parsedPattern.firstDayOfWeek = firstDayOfWeek;\n\n if (rawPattern.DaysOfWeek === undefined || rawPattern.DaysOfWeek.length === 0) {\n throw new Error(buildInvalidParameterErrorMessage(DAYS_OF_WEEK, REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));\n }\n if (!Array.isArray(rawPattern.DaysOfWeek)) {\n throw new Error(buildInvalidParameterErrorMessage(DAYS_OF_WEEK, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n }\n const daysOfWeek = [...new Set(rawPattern.DaysOfWeek.map(day => DayOfWeek[day]))]; // dedup array\n if (daysOfWeek.some(day => day === undefined)) {\n throw new Error(buildInvalidParameterErrorMessage(DAYS_OF_WEEK, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n }\n if (timeWindowDuration > interval * DAYS_PER_WEEK * ONE_DAY_IN_MILLISECONDS ||\n !isDurationCompliantWithDaysOfWeek(timeWindowDuration, interval, daysOfWeek, firstDayOfWeek)) {\n throw new Error(buildInvalidParameterErrorMessage(\"End\", TIME_WINDOW_DURATION_OUT_OF_RANGE_ERROR_MESSAGE));\n }\n parsedPattern.daysOfWeek = daysOfWeek;\n\n // check whether \"Start\" is a valid first occurrence\n const alignedStartDay = getDayOfWeek(startTime, timeZoneOffset);\n if (!daysOfWeek.find(day => day === alignedStartDay)) {\n throw new Error(buildInvalidParameterErrorMessage(\"Start\", START_NOT_MATCHED_ERROR_MESSAGE));\n }\n }\n return parsedPattern;\n}\n\nfunction parseRecurrenceRange(startTime: Date, recurrenceParameters: RecurrenceParameters): RecurrenceRange {\n const rawRange = recurrenceParameters.Range;\n if (rawRange === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(RANGE, REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));\n }\n if (rawRange.Type === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(RANGE_TYPE, REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE));\n }\n const rangeType = RecurrenceRangeType[rawRange.Type];\n if (rangeType === undefined) {\n throw new Error(buildInvalidParameterErrorMessage(RANGE_TYPE, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n }\n const parsedRange: RecurrenceRange = { type: rangeType };\n if (rangeType === RecurrenceRangeType.EndDate) {\n let endDate: Date;\n if (rawRange.EndDate !== undefined) {\n endDate = new Date(rawRange.EndDate);\n if (isNaN(endDate.getTime())) {\n throw new Error(buildInvalidParameterErrorMessage(END_DATE, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n }\n if (endDate < startTime) {\n throw new Error(buildInvalidParameterErrorMessage(END_DATE, VALUE_OUT_OF_RANGE_ERROR_MESSAGE));\n }\n } else {\n endDate = new Date(8.64e15); // the maximum date in ECMAScript: https://262.ecma-international.org/5.1/#sec-15.9.1.1\n }\n parsedRange.endDate = endDate;\n } else if (rangeType === RecurrenceRangeType.Numbered) {\n let numberOfOccurrences = rawRange.NumberOfOccurrences;\n if (numberOfOccurrences !== undefined) {\n if (typeof numberOfOccurrences !== \"number\") {\n throw new Error(buildInvalidParameterErrorMessage(NUMBER_OF_OCCURRENCES, UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n } else if (numberOfOccurrences <= 0 || !Number.isInteger(numberOfOccurrences)) {\n throw new Error(buildInvalidParameterErrorMessage(NUMBER_OF_OCCURRENCES, VALUE_OUT_OF_RANGE_ERROR_MESSAGE));\n }\n } else {\n numberOfOccurrences = Number.MAX_SAFE_INTEGER;\n }\n parsedRange.numberOfOccurrences = numberOfOccurrences;\n }\n return parsedRange;\n}\n\nfunction isDurationCompliantWithDaysOfWeek(duration: number, interval: number, daysOfWeek: DayOfWeek[], firstDayOfWeek: DayOfWeek): boolean {\n if (daysOfWeek.length === 1) {\n return true;\n }\n const sortedDaysOfWeek = sortDaysOfWeek(daysOfWeek, firstDayOfWeek);\n let prev = sortedDaysOfWeek[0]; // the closest occurrence day to the first day of week\n let minGap = DAYS_PER_WEEK * ONE_DAY_IN_MILLISECONDS;\n for (let i = 1; i < sortedDaysOfWeek.length; i++) { // skip the first day\n const gap = calculateWeeklyDayOffset(sortedDaysOfWeek[i], prev) * ONE_DAY_IN_MILLISECONDS;\n minGap = gap < minGap ? gap : minGap;\n prev = sortedDaysOfWeek[i];\n }\n // It may across weeks. Check the next week if the interval is one week.\n if (interval == 1) {\n const gap = calculateWeeklyDayOffset(sortedDaysOfWeek[0], prev) * ONE_DAY_IN_MILLISECONDS;\n minGap = gap < minGap ? gap : minGap;\n }\n return minGap >= duration;\n}\n"],"names":["buildInvalidParameterErrorMessage","REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE","VALUE_OUT_OF_RANGE_ERROR_MESSAGE","ONE_DAY_IN_MILLISECONDS","RecurrencePatternType","UNRECOGNIZABLE_VALUE_ERROR_MESSAGE","DayOfWeek","DAYS_PER_WEEK","getDayOfWeek","RecurrenceRangeType","sortDaysOfWeek","calculateWeeklyDayOffset"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"
|
|
1
|
+
{"version":3,"file":"targetingFilter.js","sources":["../../../src/filter/targetingFilter.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IFeatureFilter } from \"./featureFilter.js\";\nimport { isTargetedPercentile } from \"../common/targetingEvaluator.js\";\nimport { ITargetingContext, ITargetingContextAccessor } from \"../common/targetingContext.js\";\n\ntype TargetingFilterParameters = {\n Audience: {\n DefaultRolloutPercentage: number;\n Users?: string[];\n Groups?: {\n Name: string;\n RolloutPercentage: number;\n }[];\n Exclusion?: {\n Users?: string[];\n Groups?: string[];\n };\n }\n}\n\ntype TargetingFilterEvaluationContext = {\n featureName: string;\n parameters: TargetingFilterParameters;\n}\n\nexport class TargetingFilter implements IFeatureFilter {\n readonly name: string = \"Microsoft.Targeting\";\n readonly #targetingContextAccessor?: ITargetingContextAccessor;\n\n constructor(targetingContextAccessor?: ITargetingContextAccessor) {\n this.#targetingContextAccessor = targetingContextAccessor;\n }\n\n async evaluate(context: TargetingFilterEvaluationContext, appContext?: ITargetingContext): Promise<boolean> {\n const { featureName, parameters } = context;\n TargetingFilter.#validateParameters(featureName, parameters);\n\n let targetingContext: ITargetingContext | undefined;\n if (appContext?.userId !== undefined || appContext?.groups !== undefined) {\n targetingContext = appContext;\n } else if (this.#targetingContextAccessor !== undefined) {\n targetingContext = this.#targetingContextAccessor.getTargetingContext();\n }\n\n if (parameters.Audience.Exclusion !== undefined) {\n // check if the user is in the exclusion list\n if (targetingContext?.userId !== undefined &&\n parameters.Audience.Exclusion.Users !== undefined &&\n parameters.Audience.Exclusion.Users.includes(targetingContext.userId)) {\n return false;\n }\n // check if the user is in a group within exclusion list\n if (targetingContext?.groups !== undefined &&\n parameters.Audience.Exclusion.Groups !== undefined) {\n for (const excludedGroup of parameters.Audience.Exclusion.Groups) {\n if (targetingContext.groups.includes(excludedGroup)) {\n return false;\n }\n }\n }\n }\n\n // check if the user is being targeted directly\n if (targetingContext?.userId !== undefined &&\n parameters.Audience.Users !== undefined &&\n parameters.Audience.Users.includes(targetingContext.userId)) {\n return true;\n }\n\n // check if the user is in a group that is being targeted\n if (targetingContext?.groups !== undefined &&\n parameters.Audience.Groups !== undefined) {\n for (const group of parameters.Audience.Groups) {\n if (targetingContext.groups.includes(group.Name)) {\n const hint = `${featureName}\\n${group.Name}`;\n if (await isTargetedPercentile(targetingContext.userId, hint, 0, group.RolloutPercentage)) {\n return true;\n }\n }\n }\n }\n\n // check if the user is being targeted by a default rollout percentage\n const hint = featureName;\n return isTargetedPercentile(targetingContext?.userId, hint, 0, parameters.Audience.DefaultRolloutPercentage);\n }\n\n static #validateParameters(featureName: string, parameters: TargetingFilterParameters): void {\n if (parameters.Audience.DefaultRolloutPercentage < 0 || parameters.Audience.DefaultRolloutPercentage > 100) {\n throw new Error(`Invalid feature flag: ${featureName}. Audience.DefaultRolloutPercentage must be a number between 0 and 100.`);\n }\n // validate RolloutPercentage for each group\n if (parameters.Audience.Groups !== undefined) {\n for (const group of parameters.Audience.Groups) {\n if (group.RolloutPercentage < 0 || group.RolloutPercentage > 100) {\n throw new Error(`Invalid feature flag: ${featureName}. RolloutPercentage of group ${group.Name} must be a number between 0 and 100.`);\n }\n }\n }\n }\n}\n"],"names":["isTargetedPercentile"],"mappings":";;;;AAAA;AACA;MA0Ba,eAAe,CAAA;IACf,IAAI,GAAW,qBAAqB,CAAC;AACrC,IAAA,yBAAyB,CAA6B;AAE/D,IAAA,WAAA,CAAY,wBAAoD,EAAA;AAC5D,QAAA,IAAI,CAAC,yBAAyB,GAAG,wBAAwB,CAAC;KAC7D;AAED,IAAA,MAAM,QAAQ,CAAC,OAAyC,EAAE,UAA8B,EAAA;AACpF,QAAA,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;AAC5C,QAAA,eAAe,CAAC,mBAAmB,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;AAE7D,QAAA,IAAI,gBAA+C,CAAC;AACpD,QAAA,IAAI,UAAU,EAAE,MAAM,KAAK,SAAS,IAAI,UAAU,EAAE,MAAM,KAAK,SAAS,EAAE;YACtE,gBAAgB,GAAG,UAAU,CAAC;SACjC;AAAM,aAAA,IAAI,IAAI,CAAC,yBAAyB,KAAK,SAAS,EAAE;AACrD,YAAA,gBAAgB,GAAG,IAAI,CAAC,yBAAyB,CAAC,mBAAmB,EAAE,CAAC;SAC3E;QAED,IAAI,UAAU,CAAC,QAAQ,CAAC,SAAS,KAAK,SAAS,EAAE;;AAE7C,YAAA,IAAI,gBAAgB,EAAE,MAAM,KAAK,SAAS;AACtC,gBAAA,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,KAAK,SAAS;AACjD,gBAAA,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE;AACvE,gBAAA,OAAO,KAAK,CAAC;aAChB;;AAED,YAAA,IAAI,gBAAgB,EAAE,MAAM,KAAK,SAAS;gBACtC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,KAAK,SAAS,EAAE;gBACpD,KAAK,MAAM,aAAa,IAAI,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE;oBAC9D,IAAI,gBAAgB,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE;AACjD,wBAAA,OAAO,KAAK,CAAC;qBAChB;iBACJ;aACJ;SACJ;;AAGD,QAAA,IAAI,gBAAgB,EAAE,MAAM,KAAK,SAAS;AACtC,YAAA,UAAU,CAAC,QAAQ,CAAC,KAAK,KAAK,SAAS;AACvC,YAAA,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE;AAC7D,YAAA,OAAO,IAAI,CAAC;SACf;;AAGD,QAAA,IAAI,gBAAgB,EAAE,MAAM,KAAK,SAAS;AACtC,YAAA,UAAU,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE;YAC1C,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE;gBAC5C,IAAI,gBAAgB,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE;oBAC9C,MAAM,IAAI,GAAG,CAAG,EAAA,WAAW,KAAK,KAAK,CAAC,IAAI,CAAA,CAAE,CAAC;AAC7C,oBAAA,IAAI,MAAMA,uCAAoB,CAAC,gBAAgB,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,EAAE;AACvF,wBAAA,OAAO,IAAI,CAAC;qBACf;iBACJ;aACJ;SACJ;;QAGD,MAAM,IAAI,GAAG,WAAW,CAAC;AACzB,QAAA,OAAOA,uCAAoB,CAAC,gBAAgB,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,QAAQ,CAAC,wBAAwB,CAAC,CAAC;KAChH;AAED,IAAA,OAAO,mBAAmB,CAAC,WAAmB,EAAE,UAAqC,EAAA;AACjF,QAAA,IAAI,UAAU,CAAC,QAAQ,CAAC,wBAAwB,GAAG,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,wBAAwB,GAAG,GAAG,EAAE;AACxG,YAAA,MAAM,IAAI,KAAK,CAAC,yBAAyB,WAAW,CAAA,uEAAA,CAAyE,CAAC,CAAC;SAClI;;QAED,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,KAAK,SAAS,EAAE;YAC1C,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,EAAE;AAC5C,gBAAA,IAAI,KAAK,CAAC,iBAAiB,GAAG,CAAC,IAAI,KAAK,CAAC,iBAAiB,GAAG,GAAG,EAAE;oBAC9D,MAAM,IAAI,KAAK,CAAC,CAAyB,sBAAA,EAAA,WAAW,CAAgC,6BAAA,EAAA,KAAK,CAAC,IAAI,CAAsC,oCAAA,CAAA,CAAC,CAAC;iBACzI;aACJ;SACJ;KACJ;AACJ;;;;"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var validator = require('./recurrence/validator.js');
|
|
4
|
+
var evaluator = require('./recurrence/evaluator.js');
|
|
5
|
+
var utils = require('./utils.js');
|
|
6
|
+
|
|
7
|
+
// Copyright (c) Microsoft Corporation.
|
|
8
|
+
// Licensed under the MIT license.
|
|
9
|
+
class TimeWindowFilter {
|
|
10
|
+
name = "Microsoft.TimeWindow";
|
|
11
|
+
evaluate(context) {
|
|
12
|
+
const { featureName, parameters } = context;
|
|
13
|
+
const startTime = parameters.Start !== undefined ? new Date(parameters.Start) : undefined;
|
|
14
|
+
const endTime = parameters.End !== undefined ? new Date(parameters.End) : undefined;
|
|
15
|
+
const baseErrorMessage = `The ${this.name} feature filter is not valid for feature ${featureName}. `;
|
|
16
|
+
if (startTime === undefined && endTime === undefined) {
|
|
17
|
+
// If neither start nor end time is specified, then the filter is not applicable.
|
|
18
|
+
console.warn(baseErrorMessage + "It must specify either 'Start', 'End', or both.");
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (startTime !== undefined && isNaN(startTime.getTime())) {
|
|
22
|
+
console.warn(baseErrorMessage + utils.buildInvalidParameterErrorMessage("Start", utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (endTime !== undefined && isNaN(endTime.getTime())) {
|
|
26
|
+
console.warn(baseErrorMessage + utils.buildInvalidParameterErrorMessage("End", utils.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const now = new Date();
|
|
30
|
+
if ((startTime === undefined || startTime <= now) && (endTime === undefined || now < endTime)) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
if (parameters.Recurrence !== undefined) {
|
|
34
|
+
let recurrence;
|
|
35
|
+
try {
|
|
36
|
+
recurrence = validator.parseRecurrenceParameter(startTime, endTime, parameters.Recurrence);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.warn(baseErrorMessage + error.message);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return evaluator.matchRecurrence(now, recurrence);
|
|
43
|
+
}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
exports.TimeWindowFilter = TimeWindowFilter;
|
|
49
|
+
//# sourceMappingURL=timeWindowFilter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeWindowFilter.js","sources":["../../../src/filter/timeWindowFilter.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IFeatureFilter } from \"./featureFilter.js\";\nimport { RecurrenceSpec } from \"./recurrence/model.js\";\nimport { parseRecurrenceParameter } from \"./recurrence/validator.js\";\nimport { matchRecurrence } from \"./recurrence/evaluator.js\";\nimport { UNRECOGNIZABLE_VALUE_ERROR_MESSAGE, buildInvalidParameterErrorMessage } from \"./utils.js\";\n\ntype TimeWindowFilterEvaluationContext = {\n featureName: string;\n parameters: TimeWindowParameters;\n};\n\ntype TimeWindowParameters = {\n Start?: string;\n End?: string;\n Recurrence?: RecurrenceParameters;\n};\n\nexport type RecurrenceParameters = {\n Pattern: {\n Type: string;\n Interval?: number;\n DaysOfWeek?: string[];\n FirstDayOfWeek?: string;\n },\n Range: {\n Type: string;\n EndDate?: string;\n NumberOfOccurrences?: number;\n }\n};\n\nexport class TimeWindowFilter implements IFeatureFilter {\n readonly name: string = \"Microsoft.TimeWindow\";\n\n evaluate(context: TimeWindowFilterEvaluationContext): boolean {\n const {featureName, parameters} = context;\n const startTime = parameters.Start !== undefined ? new Date(parameters.Start) : undefined;\n const endTime = parameters.End !== undefined ? new Date(parameters.End) : undefined;\n\n const baseErrorMessage = `The ${this.name} feature filter is not valid for feature ${featureName}. `;\n\n if (startTime === undefined && endTime === undefined) {\n // If neither start nor end time is specified, then the filter is not applicable.\n console.warn(baseErrorMessage + \"It must specify either 'Start', 'End', or both.\");\n return false;\n }\n\n if (startTime !== undefined && isNaN(startTime.getTime())) {\n console.warn(baseErrorMessage + buildInvalidParameterErrorMessage(\"Start\", UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n return false;\n }\n\n if (endTime !== undefined && isNaN(endTime.getTime())) {\n console.warn(baseErrorMessage + buildInvalidParameterErrorMessage(\"End\", UNRECOGNIZABLE_VALUE_ERROR_MESSAGE));\n return false;\n }\n\n const now = new Date();\n\n if ((startTime === undefined || startTime <= now) && (endTime === undefined || now < endTime)) {\n return true;\n }\n\n if (parameters.Recurrence !== undefined) {\n let recurrence: RecurrenceSpec;\n try {\n recurrence = parseRecurrenceParameter(startTime, endTime, parameters.Recurrence);\n } catch (error) {\n console.warn(baseErrorMessage + error.message);\n return false;\n }\n return matchRecurrence(now, recurrence);\n }\n\n return false;\n }\n}\n"],"names":["buildInvalidParameterErrorMessage","UNRECOGNIZABLE_VALUE_ERROR_MESSAGE","parseRecurrenceParameter","matchRecurrence"],"mappings":";;;;;;AAAA;AACA;MAiCa,gBAAgB,CAAA;IAChB,IAAI,GAAW,sBAAsB,CAAC;AAE/C,IAAA,QAAQ,CAAC,OAA0C,EAAA;AAC/C,QAAA,MAAM,EAAC,WAAW,EAAE,UAAU,EAAC,GAAG,OAAO,CAAC;QAC1C,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,KAAK,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;QAC1F,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,KAAK,SAAS,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC;QAEpF,MAAM,gBAAgB,GAAG,CAAO,IAAA,EAAA,IAAI,CAAC,IAAI,CAAA,yCAAA,EAA4C,WAAW,CAAA,EAAA,CAAI,CAAC;QAErG,IAAI,SAAS,KAAK,SAAS,IAAI,OAAO,KAAK,SAAS,EAAE;;AAElD,YAAA,OAAO,CAAC,IAAI,CAAC,gBAAgB,GAAG,iDAAiD,CAAC,CAAC;AACnF,YAAA,OAAO,KAAK,CAAC;SAChB;AAED,QAAA,IAAI,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE;AACvD,YAAA,OAAO,CAAC,IAAI,CAAC,gBAAgB,GAAGA,uCAAiC,CAAC,OAAO,EAAEC,wCAAkC,CAAC,CAAC,CAAC;AAChH,YAAA,OAAO,KAAK,CAAC;SAChB;AAED,QAAA,IAAI,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,EAAE;AACnD,YAAA,OAAO,CAAC,IAAI,CAAC,gBAAgB,GAAGD,uCAAiC,CAAC,KAAK,EAAEC,wCAAkC,CAAC,CAAC,CAAC;AAC9G,YAAA,OAAO,KAAK,CAAC;SAChB;AAED,QAAA,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QAEvB,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,SAAS,IAAI,GAAG,MAAM,OAAO,KAAK,SAAS,IAAI,GAAG,GAAG,OAAO,CAAC,EAAE;AAC3F,YAAA,OAAO,IAAI,CAAC;SACf;AAED,QAAA,IAAI,UAAU,CAAC,UAAU,KAAK,SAAS,EAAE;AACrC,YAAA,IAAI,UAA0B,CAAC;AAC/B,YAAA,IAAI;gBACA,UAAU,GAAGC,kCAAwB,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;aACpF;YAAC,OAAO,KAAK,EAAE;gBACZ,OAAO,CAAC,IAAI,CAAC,gBAAgB,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;AAC/C,gBAAA,OAAO,KAAK,CAAC;aAChB;AACD,YAAA,OAAOC,yBAAe,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;SAC3C;AAED,QAAA,OAAO,KAAK,CAAC;KAChB;AACJ;;;;"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Copyright (c) Microsoft Corporation.
|
|
4
|
+
// Licensed under the MIT license.
|
|
5
|
+
const VALUE_OUT_OF_RANGE_ERROR_MESSAGE = "The value is out of the accepted range.";
|
|
6
|
+
const UNRECOGNIZABLE_VALUE_ERROR_MESSAGE = "The value is unrecognizable.";
|
|
7
|
+
const REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE = "Value cannot be undefined or empty.";
|
|
8
|
+
function buildInvalidParameterErrorMessage(parameterName, additionalInfo) {
|
|
9
|
+
return `The ${parameterName} parameter is not valid. ` + (additionalInfo ?? "");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exports.REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE = REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE;
|
|
13
|
+
exports.UNRECOGNIZABLE_VALUE_ERROR_MESSAGE = UNRECOGNIZABLE_VALUE_ERROR_MESSAGE;
|
|
14
|
+
exports.VALUE_OUT_OF_RANGE_ERROR_MESSAGE = VALUE_OUT_OF_RANGE_ERROR_MESSAGE;
|
|
15
|
+
exports.buildInvalidParameterErrorMessage = buildInvalidParameterErrorMessage;
|
|
16
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../../src/filter/utils.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nexport const VALUE_OUT_OF_RANGE_ERROR_MESSAGE = \"The value is out of the accepted range.\";\nexport const UNRECOGNIZABLE_VALUE_ERROR_MESSAGE = \"The value is unrecognizable.\";\nexport const REQUIRED_PARAMETER_MISSING_ERROR_MESSAGE = \"Value cannot be undefined or empty.\";\n\nexport function buildInvalidParameterErrorMessage(parameterName: string, additionalInfo?: string): string {\n return `The ${parameterName} parameter is not valid. ` + (additionalInfo ?? \"\");\n}\n"],"names":[],"mappings":";;AAAA;AACA;AAEO,MAAM,gCAAgC,GAAG,0CAA0C;AACnF,MAAM,kCAAkC,GAAG,+BAA+B;AAC1E,MAAM,wCAAwC,GAAG,sCAAsC;AAE9E,SAAA,iCAAiC,CAAC,aAAqB,EAAE,cAAuB,EAAA;IAC5F,OAAO,CAAA,IAAA,EAAO,aAAa,CAA2B,yBAAA,CAAA,IAAI,cAAc,IAAI,EAAE,CAAC,CAAC;AACpF;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"
|
|
1
|
+
{"version":3,"file":"variant.js","sources":["../../../src/variant/variant.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nexport class Variant {\n constructor(\n public name: string,\n public configuration: unknown\n ) {}\n}\n"],"names":[],"mappings":";;AAAA;AACA;MAEa,OAAO,CAAA;AAEL,IAAA,IAAA,CAAA;AACA,IAAA,aAAA,CAAA;IAFX,WACW,CAAA,IAAY,EACZ,aAAsB,EAAA;QADtB,IAAI,CAAA,IAAA,GAAJ,IAAI,CAAQ;QACZ,IAAa,CAAA,aAAA,GAAb,aAAa,CAAS;KAC7B;AACP;;;;"}
|
package/dist/commonjs/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.js","sources":["../../src/version.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nexport const VERSION = \"2.
|
|
1
|
+
{"version":3,"file":"version.js","sources":["../../src/version.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nexport const VERSION = \"2.2.0\";\nexport const EVALUATION_EVENT_VERSION = \"1.0.0\";\n"],"names":[],"mappings":";;AAAA;AACA;AAEO,MAAM,OAAO,GAAG,QAAQ;AACxB,MAAM,wBAAwB,GAAG;;;;;"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { TimeWindowFilter } from './filter/
|
|
2
|
-
import { TargetingFilter } from './filter/
|
|
3
|
-
import { Variant } from './variant/
|
|
1
|
+
import { TimeWindowFilter } from './filter/timeWindowFilter.js';
|
|
2
|
+
import { TargetingFilter } from './filter/targetingFilter.js';
|
|
3
|
+
import { Variant } from './variant/variant.js';
|
|
4
4
|
import { isTargetedUser, isTargetedGroup, isTargetedPercentile } from './common/targetingEvaluator.js';
|
|
5
5
|
|
|
6
6
|
// Copyright (c) Microsoft Corporation.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"featureManager.js","sources":["../../src/featureManager.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { TimeWindowFilter } from \"./filter/TimeWindowFilter.js\";\nimport { IFeatureFilter } from \"./filter/FeatureFilter.js\";\nimport { FeatureFlag, RequirementType, VariantDefinition } from \"./schema/model.js\";\nimport { IFeatureFlagProvider } from \"./featureProvider.js\";\nimport { TargetingFilter } from \"./filter/TargetingFilter.js\";\nimport { Variant } from \"./variant/Variant.js\";\nimport { IFeatureManager } from \"./IFeatureManager.js\";\nimport { ITargetingContext, ITargetingContextAccessor } from \"./common/targetingContext.js\";\nimport { isTargetedGroup, isTargetedPercentile, isTargetedUser } from \"./common/targetingEvaluator.js\";\n\nexport class FeatureManager implements IFeatureManager {\n readonly #provider: IFeatureFlagProvider;\n readonly #featureFilters: Map<string, IFeatureFilter> = new Map();\n readonly #onFeatureEvaluated?: (event: EvaluationResult) => void;\n readonly #targetingContextAccessor?: ITargetingContextAccessor;\n\n constructor(provider: IFeatureFlagProvider, options?: FeatureManagerOptions) {\n this.#provider = provider;\n this.#onFeatureEvaluated = options?.onFeatureEvaluated;\n this.#targetingContextAccessor = options?.targetingContextAccessor;\n\n const builtinFilters = [new TimeWindowFilter(), new TargetingFilter(options?.targetingContextAccessor)];\n // If a custom filter shares a name with an existing filter, the custom filter overrides the existing one.\n for (const filter of [...builtinFilters, ...(options?.customFilters ?? [])]) {\n this.#featureFilters.set(filter.name, filter);\n }\n }\n\n async listFeatureNames(): Promise<string[]> {\n const features = await this.#provider.getFeatureFlags();\n const featureNameSet = new Set(features.map((feature) => feature.id));\n return Array.from(featureNameSet);\n }\n\n // If multiple feature flags are found, the first one takes precedence.\n async isEnabled(featureName: string, context?: unknown): Promise<boolean> {\n const result = await this.#evaluateFeature(featureName, context);\n return result.enabled;\n }\n\n async getVariant(featureName: string, context?: ITargetingContext): Promise<Variant | undefined> {\n const result = await this.#evaluateFeature(featureName, context);\n return result.variant;\n }\n\n async #assignVariant(featureFlag: FeatureFlag, context: ITargetingContext): Promise<VariantAssignment> {\n // user allocation\n if (featureFlag.allocation?.user !== undefined) {\n for (const userAllocation of featureFlag.allocation.user) {\n if (isTargetedUser(context.userId, userAllocation.users)) {\n return getVariantAssignment(featureFlag, userAllocation.variant, VariantAssignmentReason.User);\n }\n }\n }\n\n // group allocation\n if (featureFlag.allocation?.group !== undefined) {\n for (const groupAllocation of featureFlag.allocation.group) {\n if (isTargetedGroup(context.groups, groupAllocation.groups)) {\n return getVariantAssignment(featureFlag, groupAllocation.variant, VariantAssignmentReason.Group);\n }\n }\n }\n\n // percentile allocation\n if (featureFlag.allocation?.percentile !== undefined) {\n for (const percentileAllocation of featureFlag.allocation.percentile) {\n const hint = featureFlag.allocation.seed ?? `allocation\\n${featureFlag.id}`;\n if (await isTargetedPercentile(context.userId, hint, percentileAllocation.from, percentileAllocation.to)) {\n return getVariantAssignment(featureFlag, percentileAllocation.variant, VariantAssignmentReason.Percentile);\n }\n }\n }\n\n return { variant: undefined, reason: VariantAssignmentReason.None };\n }\n\n async #isEnabled(featureFlag: FeatureFlag, appContext?: unknown): Promise<boolean> {\n if (featureFlag.enabled !== true) {\n // If the feature is not explicitly enabled, then it is disabled by default.\n return false;\n }\n\n const clientFilters = featureFlag.conditions?.client_filters;\n if (clientFilters === undefined || clientFilters.length <= 0) {\n // If there are no client filters, then the feature is enabled.\n return true;\n }\n\n const requirementType: RequirementType = featureFlag.conditions?.requirement_type ?? \"Any\"; // default to any.\n\n /**\n * While iterating through the client filters, we short-circuit the evaluation based on the requirement type.\n * - When requirement type is \"All\", the feature is enabled if all client filters are matched. If any client filter is not matched, the feature is disabled, otherwise it is enabled. `shortCircuitEvaluationResult` is false.\n * - When requirement type is \"Any\", the feature is enabled if any client filter is matched. If any client filter is matched, the feature is enabled, otherwise it is disabled. `shortCircuitEvaluationResult` is true.\n */\n const shortCircuitEvaluationResult: boolean = requirementType === \"Any\";\n\n for (const clientFilter of clientFilters) {\n const matchedFeatureFilter = this.#featureFilters.get(clientFilter.name);\n const contextWithFeatureName = { featureName: featureFlag.id, parameters: clientFilter.parameters };\n if (matchedFeatureFilter === undefined) {\n console.warn(`Feature filter ${clientFilter.name} is not found.`);\n return false;\n }\n if (await matchedFeatureFilter.evaluate(contextWithFeatureName, appContext) === shortCircuitEvaluationResult) {\n return shortCircuitEvaluationResult;\n }\n }\n\n // If we get here, then we have not found a client filter that matches the requirement type.\n return !shortCircuitEvaluationResult;\n }\n\n async #evaluateFeature(featureName: string, appContext: unknown): Promise<EvaluationResult> {\n const featureFlag = await this.#provider.getFeatureFlag(featureName);\n const result = new EvaluationResult(featureFlag);\n\n if (featureFlag === undefined) {\n return result;\n }\n\n // Ensure that the feature flag is in the correct format. Feature providers should validate the feature flags, but we do it here as a safeguard.\n // TODO: move to the feature flag provider implementation.\n validateFeatureFlagFormat(featureFlag);\n\n // Evaluate if the feature is enabled.\n result.enabled = await this.#isEnabled(featureFlag, appContext);\n\n // Get targeting context from the app context or the targeting context accessor\n const targetingContext = this.#getTargetingContext(appContext);\n result.targetingId = targetingContext?.userId;\n\n // Determine Variant\n let variantDef: VariantDefinition | undefined;\n let reason: VariantAssignmentReason = VariantAssignmentReason.None;\n\n // featureFlag.variant not empty\n if (featureFlag.variants !== undefined && featureFlag.variants.length > 0) {\n if (!result.enabled) {\n // not enabled, assign default if specified\n if (featureFlag.allocation?.default_when_disabled !== undefined) {\n variantDef = featureFlag.variants.find(v => v.name == featureFlag.allocation?.default_when_disabled);\n reason = VariantAssignmentReason.DefaultWhenDisabled;\n } else {\n // no default specified\n variantDef = undefined;\n reason = VariantAssignmentReason.DefaultWhenDisabled;\n }\n } else {\n // enabled, assign based on allocation\n if (targetingContext !== undefined && featureFlag.allocation !== undefined) {\n const variantAndReason = await this.#assignVariant(featureFlag, targetingContext);\n variantDef = variantAndReason.variant;\n reason = variantAndReason.reason;\n }\n\n // allocation failed, assign default if specified\n if (variantDef === undefined && reason === VariantAssignmentReason.None) {\n if (featureFlag.allocation?.default_when_enabled !== undefined) {\n variantDef = featureFlag.variants.find(v => v.name == featureFlag.allocation?.default_when_enabled);\n reason = VariantAssignmentReason.DefaultWhenEnabled;\n } else {\n variantDef = undefined;\n reason = VariantAssignmentReason.DefaultWhenEnabled;\n }\n }\n }\n }\n\n result.variant = variantDef !== undefined ? new Variant(variantDef.name, variantDef.configuration_value) : undefined;\n result.variantAssignmentReason = reason;\n\n // Status override for isEnabled\n if (variantDef !== undefined && featureFlag.enabled) {\n if (variantDef.status_override === \"Enabled\") {\n result.enabled = true;\n } else if (variantDef.status_override === \"Disabled\") {\n result.enabled = false;\n }\n }\n\n // The callback will only be executed if telemetry is enabled for the feature flag\n if (featureFlag.telemetry?.enabled && this.#onFeatureEvaluated !== undefined) {\n this.#onFeatureEvaluated(result);\n }\n\n return result;\n }\n\n #getTargetingContext(context: unknown): ITargetingContext | undefined {\n let targetingContext: ITargetingContext | undefined = context as ITargetingContext;\n if (targetingContext?.userId === undefined &&\n targetingContext?.groups === undefined &&\n this.#targetingContextAccessor !== undefined) {\n targetingContext = this.#targetingContextAccessor.getTargetingContext();\n }\n return targetingContext;\n }\n}\n\nexport interface FeatureManagerOptions {\n /**\n * The custom filters to be used by the feature manager.\n */\n customFilters?: IFeatureFilter[];\n\n /**\n * The callback function that is called when a feature flag is evaluated.\n * The callback function is called only when telemetry is enabled for the feature flag.\n */\n onFeatureEvaluated?: (event: EvaluationResult) => void;\n\n /**\n * The accessor function that provides the @see ITargetingContext for targeting evaluation.\n */\n targetingContextAccessor?: ITargetingContextAccessor;\n}\n\nexport class EvaluationResult {\n constructor(\n // feature flag definition\n public readonly feature: FeatureFlag | undefined,\n\n // enabled state\n public enabled: boolean = false,\n\n // variant assignment\n public targetingId: string | undefined = undefined,\n public variant: Variant | undefined = undefined,\n public variantAssignmentReason: VariantAssignmentReason = VariantAssignmentReason.None\n ) { }\n}\n\nexport enum VariantAssignmentReason {\n /**\n * Variant allocation did not happen. No variant is assigned.\n */\n None = \"None\",\n\n /**\n * The default variant is assigned when a feature flag is disabled.\n */\n DefaultWhenDisabled = \"DefaultWhenDisabled\",\n\n /**\n * The default variant is assigned because of no applicable user/group/percentile allocation when a feature flag is enabled.\n */\n DefaultWhenEnabled = \"DefaultWhenEnabled\",\n\n /**\n * The variant is assigned because of the user allocation when a feature flag is enabled.\n */\n User = \"User\",\n\n /**\n * The variant is assigned because of the group allocation when a feature flag is enabled.\n */\n Group = \"Group\",\n\n /**\n * The variant is assigned because of the percentile allocation when a feature flag is enabled.\n */\n Percentile = \"Percentile\"\n}\n\n/**\n * Validates the format of the feature flag definition.\n *\n * FeatureFlag data objects are from IFeatureFlagProvider, depending on the implementation.\n * Thus the properties are not guaranteed to have the expected types.\n *\n * @param featureFlag The feature flag definition to validate.\n */\nfunction validateFeatureFlagFormat(featureFlag: any): void {\n if (featureFlag.enabled !== undefined && typeof featureFlag.enabled !== \"boolean\") {\n throw new Error(`Feature flag ${featureFlag.id} has an invalid 'enabled' value.`);\n }\n // TODO: add more validations.\n // TODO: should be moved to the feature flag provider.\n}\n\n/**\n * Try to get the variant assignment for the given variant name. If the variant is not found, override the reason with VariantAssignmentReason.None.\n *\n * @param featureFlag feature flag definition\n * @param variantName variant name\n * @param reason variant assignment reason\n * @returns variant assignment containing the variant definition and the reason\n */\nfunction getVariantAssignment(featureFlag: FeatureFlag, variantName: string, reason: VariantAssignmentReason): VariantAssignment {\n const variant = featureFlag.variants?.find(v => v.name == variantName);\n if (variant !== undefined) {\n return { variant, reason };\n } else {\n console.warn(`Variant ${variantName} not found for feature ${featureFlag.id}.`);\n return { variant: undefined, reason: VariantAssignmentReason.None };\n }\n}\n\ntype VariantAssignment = {\n variant: VariantDefinition | undefined;\n reason: VariantAssignmentReason;\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"featureManager.js","sources":["../../src/featureManager.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { TimeWindowFilter } from \"./filter/timeWindowFilter.js\";\nimport { IFeatureFilter } from \"./filter/featureFilter.js\";\nimport { FeatureFlag, RequirementType, VariantDefinition } from \"./schema/model.js\";\nimport { IFeatureFlagProvider, IFeatureManager } from \"./model.js\";\nimport { TargetingFilter } from \"./filter/targetingFilter.js\";\nimport { Variant } from \"./variant/variant.js\";\nimport { ITargetingContext, ITargetingContextAccessor } from \"./common/targetingContext.js\";\nimport { isTargetedGroup, isTargetedPercentile, isTargetedUser } from \"./common/targetingEvaluator.js\";\n\nexport class FeatureManager implements IFeatureManager {\n readonly #provider: IFeatureFlagProvider;\n readonly #featureFilters: Map<string, IFeatureFilter> = new Map();\n readonly #onFeatureEvaluated?: (event: EvaluationResult) => void;\n readonly #targetingContextAccessor?: ITargetingContextAccessor;\n\n constructor(provider: IFeatureFlagProvider, options?: FeatureManagerOptions) {\n this.#provider = provider;\n this.#onFeatureEvaluated = options?.onFeatureEvaluated;\n this.#targetingContextAccessor = options?.targetingContextAccessor;\n\n const builtinFilters = [new TimeWindowFilter(), new TargetingFilter(options?.targetingContextAccessor)];\n // If a custom filter shares a name with an existing filter, the custom filter overrides the existing one.\n for (const filter of [...builtinFilters, ...(options?.customFilters ?? [])]) {\n this.#featureFilters.set(filter.name, filter);\n }\n }\n\n async listFeatureNames(): Promise<string[]> {\n const features = await this.#provider.getFeatureFlags();\n const featureNameSet = new Set(features.map((feature) => feature.id));\n return Array.from(featureNameSet);\n }\n\n // If multiple feature flags are found, the first one takes precedence.\n async isEnabled(featureName: string, context?: unknown): Promise<boolean> {\n const result = await this.#evaluateFeature(featureName, context);\n return result.enabled;\n }\n\n async getVariant(featureName: string, context?: ITargetingContext): Promise<Variant | undefined> {\n const result = await this.#evaluateFeature(featureName, context);\n return result.variant;\n }\n\n async #assignVariant(featureFlag: FeatureFlag, context: ITargetingContext): Promise<VariantAssignment> {\n // user allocation\n if (featureFlag.allocation?.user !== undefined) {\n for (const userAllocation of featureFlag.allocation.user) {\n if (isTargetedUser(context.userId, userAllocation.users)) {\n return getVariantAssignment(featureFlag, userAllocation.variant, VariantAssignmentReason.User);\n }\n }\n }\n\n // group allocation\n if (featureFlag.allocation?.group !== undefined) {\n for (const groupAllocation of featureFlag.allocation.group) {\n if (isTargetedGroup(context.groups, groupAllocation.groups)) {\n return getVariantAssignment(featureFlag, groupAllocation.variant, VariantAssignmentReason.Group);\n }\n }\n }\n\n // percentile allocation\n if (featureFlag.allocation?.percentile !== undefined) {\n for (const percentileAllocation of featureFlag.allocation.percentile) {\n const hint = featureFlag.allocation.seed ?? `allocation\\n${featureFlag.id}`;\n if (await isTargetedPercentile(context.userId, hint, percentileAllocation.from, percentileAllocation.to)) {\n return getVariantAssignment(featureFlag, percentileAllocation.variant, VariantAssignmentReason.Percentile);\n }\n }\n }\n\n return { variant: undefined, reason: VariantAssignmentReason.None };\n }\n\n async #isEnabled(featureFlag: FeatureFlag, appContext?: unknown): Promise<boolean> {\n if (featureFlag.enabled !== true) {\n // If the feature is not explicitly enabled, then it is disabled by default.\n return false;\n }\n\n const clientFilters = featureFlag.conditions?.client_filters;\n if (clientFilters === undefined || clientFilters.length <= 0) {\n // If there are no client filters, then the feature is enabled.\n return true;\n }\n\n const requirementType: RequirementType = featureFlag.conditions?.requirement_type ?? \"Any\"; // default to any.\n\n /**\n * While iterating through the client filters, we short-circuit the evaluation based on the requirement type.\n * - When requirement type is \"All\", the feature is enabled if all client filters are matched. If any client filter is not matched, the feature is disabled, otherwise it is enabled. `shortCircuitEvaluationResult` is false.\n * - When requirement type is \"Any\", the feature is enabled if any client filter is matched. If any client filter is matched, the feature is enabled, otherwise it is disabled. `shortCircuitEvaluationResult` is true.\n */\n const shortCircuitEvaluationResult: boolean = requirementType === \"Any\";\n\n for (const clientFilter of clientFilters) {\n const matchedFeatureFilter = this.#featureFilters.get(clientFilter.name);\n const contextWithFeatureName = { featureName: featureFlag.id, parameters: clientFilter.parameters };\n if (matchedFeatureFilter === undefined) {\n console.warn(`Feature filter ${clientFilter.name} is not found.`);\n return false;\n }\n if (await matchedFeatureFilter.evaluate(contextWithFeatureName, appContext) === shortCircuitEvaluationResult) {\n return shortCircuitEvaluationResult;\n }\n }\n\n // If we get here, then we have not found a client filter that matches the requirement type.\n return !shortCircuitEvaluationResult;\n }\n\n async #evaluateFeature(featureName: string, appContext: unknown): Promise<EvaluationResult> {\n const featureFlag = await this.#provider.getFeatureFlag(featureName);\n const result = new EvaluationResult(featureFlag);\n\n if (featureFlag === undefined) {\n return result;\n }\n\n // Ensure that the feature flag is in the correct format. Feature providers should validate the feature flags, but we do it here as a safeguard.\n // TODO: move to the feature flag provider implementation.\n validateFeatureFlagFormat(featureFlag);\n\n // Evaluate if the feature is enabled.\n result.enabled = await this.#isEnabled(featureFlag, appContext);\n\n // Get targeting context from the app context or the targeting context accessor\n const targetingContext = this.#getTargetingContext(appContext);\n result.targetingId = targetingContext?.userId;\n\n // Determine Variant\n let variantDef: VariantDefinition | undefined;\n let reason: VariantAssignmentReason = VariantAssignmentReason.None;\n\n // featureFlag.variant not empty\n if (featureFlag.variants !== undefined && featureFlag.variants.length > 0) {\n if (!result.enabled) {\n // not enabled, assign default if specified\n if (featureFlag.allocation?.default_when_disabled !== undefined) {\n variantDef = featureFlag.variants.find(v => v.name == featureFlag.allocation?.default_when_disabled);\n reason = VariantAssignmentReason.DefaultWhenDisabled;\n } else {\n // no default specified\n variantDef = undefined;\n reason = VariantAssignmentReason.DefaultWhenDisabled;\n }\n } else {\n // enabled, assign based on allocation\n if (targetingContext !== undefined && featureFlag.allocation !== undefined) {\n const variantAndReason = await this.#assignVariant(featureFlag, targetingContext);\n variantDef = variantAndReason.variant;\n reason = variantAndReason.reason;\n }\n\n // allocation failed, assign default if specified\n if (variantDef === undefined && reason === VariantAssignmentReason.None) {\n if (featureFlag.allocation?.default_when_enabled !== undefined) {\n variantDef = featureFlag.variants.find(v => v.name == featureFlag.allocation?.default_when_enabled);\n reason = VariantAssignmentReason.DefaultWhenEnabled;\n } else {\n variantDef = undefined;\n reason = VariantAssignmentReason.DefaultWhenEnabled;\n }\n }\n }\n }\n\n result.variant = variantDef !== undefined ? new Variant(variantDef.name, variantDef.configuration_value) : undefined;\n result.variantAssignmentReason = reason;\n\n // Status override for isEnabled\n if (variantDef !== undefined && featureFlag.enabled) {\n if (variantDef.status_override === \"Enabled\") {\n result.enabled = true;\n } else if (variantDef.status_override === \"Disabled\") {\n result.enabled = false;\n }\n }\n\n // The callback will only be executed if telemetry is enabled for the feature flag\n if (featureFlag.telemetry?.enabled && this.#onFeatureEvaluated !== undefined) {\n this.#onFeatureEvaluated(result);\n }\n\n return result;\n }\n\n #getTargetingContext(context: unknown): ITargetingContext | undefined {\n let targetingContext: ITargetingContext | undefined = context as ITargetingContext;\n if (targetingContext?.userId === undefined &&\n targetingContext?.groups === undefined &&\n this.#targetingContextAccessor !== undefined) {\n targetingContext = this.#targetingContextAccessor.getTargetingContext();\n }\n return targetingContext;\n }\n}\n\nexport interface FeatureManagerOptions {\n /**\n * The custom filters to be used by the feature manager.\n */\n customFilters?: IFeatureFilter[];\n\n /**\n * The callback function that is called when a feature flag is evaluated.\n * The callback function is called only when telemetry is enabled for the feature flag.\n */\n onFeatureEvaluated?: (event: EvaluationResult) => void;\n\n /**\n * The accessor function that provides the @see ITargetingContext for targeting evaluation.\n */\n targetingContextAccessor?: ITargetingContextAccessor;\n}\n\nexport class EvaluationResult {\n constructor(\n // feature flag definition\n public readonly feature: FeatureFlag | undefined,\n\n // enabled state\n public enabled: boolean = false,\n\n // variant assignment\n public targetingId: string | undefined = undefined,\n public variant: Variant | undefined = undefined,\n public variantAssignmentReason: VariantAssignmentReason = VariantAssignmentReason.None\n ) { }\n}\n\nexport enum VariantAssignmentReason {\n /**\n * Variant allocation did not happen. No variant is assigned.\n */\n None = \"None\",\n\n /**\n * The default variant is assigned when a feature flag is disabled.\n */\n DefaultWhenDisabled = \"DefaultWhenDisabled\",\n\n /**\n * The default variant is assigned because of no applicable user/group/percentile allocation when a feature flag is enabled.\n */\n DefaultWhenEnabled = \"DefaultWhenEnabled\",\n\n /**\n * The variant is assigned because of the user allocation when a feature flag is enabled.\n */\n User = \"User\",\n\n /**\n * The variant is assigned because of the group allocation when a feature flag is enabled.\n */\n Group = \"Group\",\n\n /**\n * The variant is assigned because of the percentile allocation when a feature flag is enabled.\n */\n Percentile = \"Percentile\"\n}\n\n/**\n * Validates the format of the feature flag definition.\n *\n * FeatureFlag data objects are from IFeatureFlagProvider, depending on the implementation.\n * Thus the properties are not guaranteed to have the expected types.\n *\n * @param featureFlag The feature flag definition to validate.\n */\nfunction validateFeatureFlagFormat(featureFlag: any): void {\n if (featureFlag.enabled !== undefined && typeof featureFlag.enabled !== \"boolean\") {\n throw new Error(`Feature flag ${featureFlag.id} has an invalid 'enabled' value.`);\n }\n // TODO: add more validations.\n // TODO: should be moved to the feature flag provider.\n}\n\n/**\n * Try to get the variant assignment for the given variant name. If the variant is not found, override the reason with VariantAssignmentReason.None.\n *\n * @param featureFlag feature flag definition\n * @param variantName variant name\n * @param reason variant assignment reason\n * @returns variant assignment containing the variant definition and the reason\n */\nfunction getVariantAssignment(featureFlag: FeatureFlag, variantName: string, reason: VariantAssignmentReason): VariantAssignment {\n const variant = featureFlag.variants?.find(v => v.name == variantName);\n if (variant !== undefined) {\n return { variant, reason };\n } else {\n console.warn(`Variant ${variantName} not found for feature ${featureFlag.id}.`);\n return { variant: undefined, reason: VariantAssignmentReason.None };\n }\n}\n\ntype VariantAssignment = {\n variant: VariantDefinition | undefined;\n reason: VariantAssignmentReason;\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"featureProvider.js","sources":["../../src/featureProvider.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IGettable } from \"./gettable.js\";\nimport { FeatureFlag, FeatureManagementConfiguration, FEATURE_MANAGEMENT_KEY, FEATURE_FLAGS_KEY } from \"./schema/model.js\";\nimport {
|
|
1
|
+
{"version":3,"file":"featureProvider.js","sources":["../../src/featureProvider.ts"],"sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT license.\n\nimport { IGettable } from \"./gettable.js\";\nimport { FeatureFlag, FeatureManagementConfiguration, FEATURE_MANAGEMENT_KEY, FEATURE_FLAGS_KEY } from \"./schema/model.js\";\nimport { IFeatureFlagProvider } from \"./model.js\";\nimport { validateFeatureFlag } from \"./schema/validator.js\";\n\n/**\n * A feature flag provider that uses a map-like configuration to provide feature flags.\n */\nexport class ConfigurationMapFeatureFlagProvider implements IFeatureFlagProvider {\n #configuration: IGettable;\n\n constructor(configuration: IGettable) {\n this.#configuration = configuration;\n }\n async getFeatureFlag(featureName: string): Promise<FeatureFlag | undefined> {\n const featureConfig = this.#configuration.get<FeatureManagementConfiguration>(FEATURE_MANAGEMENT_KEY);\n const featureFlag = featureConfig?.[FEATURE_FLAGS_KEY]?.findLast((feature) => feature.id === featureName);\n validateFeatureFlag(featureFlag);\n return featureFlag;\n }\n\n async getFeatureFlags(): Promise<FeatureFlag[]> {\n const featureConfig = this.#configuration.get<FeatureManagementConfiguration>(FEATURE_MANAGEMENT_KEY);\n const featureFlags = featureConfig?.[FEATURE_FLAGS_KEY] ?? [];\n featureFlags.forEach(featureFlag => {\n validateFeatureFlag(featureFlag);\n });\n return featureFlags;\n }\n}\n\n/**\n * A feature flag provider that uses an object-like configuration to provide feature flags.\n */\nexport class ConfigurationObjectFeatureFlagProvider implements IFeatureFlagProvider {\n #configuration: Record<string, unknown>;\n\n constructor(configuration: Record<string, unknown>) {\n this.#configuration = configuration;\n }\n\n async getFeatureFlag(featureName: string): Promise<FeatureFlag | undefined> {\n const featureFlags = this.#configuration[FEATURE_MANAGEMENT_KEY]?.[FEATURE_FLAGS_KEY];\n const featureFlag = featureFlags?.findLast((feature: FeatureFlag) => feature.id === featureName);\n validateFeatureFlag(featureFlag);\n return featureFlag;\n }\n\n async getFeatureFlags(): Promise<FeatureFlag[]> {\n const featureFlags = this.#configuration[FEATURE_MANAGEMENT_KEY]?.[FEATURE_FLAGS_KEY] ?? [];\n featureFlags.forEach(featureFlag => {\n validateFeatureFlag(featureFlag);\n });\n return featureFlags;\n }\n}\n"],"names":[],"mappings":";;;AAAA;AACA;AAOA;;AAEG;MACU,mCAAmC,CAAA;AAC5C,IAAA,cAAc,CAAY;AAE1B,IAAA,WAAA,CAAY,aAAwB,EAAA;AAChC,QAAA,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;KACvC;IACD,MAAM,cAAc,CAAC,WAAmB,EAAA;QACpC,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAiC,sBAAsB,CAAC,CAAC;QACtG,MAAM,WAAW,GAAG,aAAa,GAAG,iBAAiB,CAAC,EAAE,QAAQ,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC;QAC1G,mBAAmB,CAAC,WAAW,CAAC,CAAC;AACjC,QAAA,OAAO,WAAW,CAAC;KACtB;AAED,IAAA,MAAM,eAAe,GAAA;QACjB,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,CAAC,GAAG,CAAiC,sBAAsB,CAAC,CAAC;QACtG,MAAM,YAAY,GAAG,aAAa,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC;AAC9D,QAAA,YAAY,CAAC,OAAO,CAAC,WAAW,IAAG;YAC/B,mBAAmB,CAAC,WAAW,CAAC,CAAC;AACrC,SAAC,CAAC,CAAC;AACH,QAAA,OAAO,YAAY,CAAC;KACvB;AACJ,CAAA;AAED;;AAEG;MACU,sCAAsC,CAAA;AAC/C,IAAA,cAAc,CAA0B;AAExC,IAAA,WAAA,CAAY,aAAsC,EAAA;AAC9C,QAAA,IAAI,CAAC,cAAc,GAAG,aAAa,CAAC;KACvC;IAED,MAAM,cAAc,CAAC,WAAmB,EAAA;AACpC,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,sBAAsB,CAAC,GAAG,iBAAiB,CAAC,CAAC;AACtF,QAAA,MAAM,WAAW,GAAG,YAAY,EAAE,QAAQ,CAAC,CAAC,OAAoB,KAAK,OAAO,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC;QACjG,mBAAmB,CAAC,WAAW,CAAC,CAAC;AACjC,QAAA,OAAO,WAAW,CAAC;KACtB;AAED,IAAA,MAAM,eAAe,GAAA;AACjB,QAAA,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,sBAAsB,CAAC,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC;AAC5F,QAAA,YAAY,CAAC,OAAO,CAAC,WAAW,IAAG;YAC/B,mBAAmB,CAAC,WAAW,CAAC,CAAC;AACrC,SAAC,CAAC,CAAC;AACH,QAAA,OAAO,YAAY,CAAC;KACvB;AACJ;;;;"}
|