Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
bd2b15a
add base class: recurrence range, recurrence pattern
ivywei0125 Apr 27, 2024
56832a9
add object mapper in time window filter for parsing the config string…
ivywei0125 Apr 28, 2024
18551d6
implement the logic about recurrence evaluator
ivywei0125 Apr 28, 2024
21519ab
add validations
ivywei0125 Apr 28, 2024
bfb0d93
add test case for validator
ivywei0125 Apr 29, 2024
23f13f7
add test case for evaluator
ivywei0125 Apr 29, 2024
20825d7
add more test case
ivywei0125 May 7, 2024
c4f14c6
add more test case
ivywei0125 May 7, 2024
122541e
add calculateClosestStart method to calculate the closest occurrence
ivywei0125 May 7, 2024
15e7fa2
rename function
ivywei0125 May 8, 2024
c6fa94f
add cache logic
ivywei0125 May 8, 2024
449d482
update README.md
ivywei0125 May 9, 2024
6613249
update cache service
ivywei0125 May 9, 2024
3365d34
ci
ivywei0125 May 9, 2024
ac68532
ci
ivywei0125 May 9, 2024
c683ee8
move to parent path to make the package name shorter
ivywei0125 May 9, 2024
6bbbaaa
update copy right
ivywei0125 May 9, 2024
218cfa9
address comments: update README.md
ivywei0125 May 11, 2024
314c1a4
address comment: update the parameter to keep consistent
ivywei0125 May 14, 2024
c5b8334
address comment: update the sescription to be clearer
ivywei0125 May 14, 2024
374af49
address comment: update logic
ivywei0125 May 21, 2024
461b248
address comment: rename symbol to make easier understanding
ivywei0125 May 21, 2024
2c07dfe
address comment: extract common function to a util class
ivywei0125 May 21, 2024
00d4f69
address comment: rename symbol to make easier understanding
ivywei0125 May 21, 2024
0c49c89
address comment: simplify code
ivywei0125 May 21, 2024
77aaa0a
address comment: move to models folder
ivywei0125 May 21, 2024
fa7b99e
revert cache logic
ivywei0125 May 22, 2024
f694fbd
address comment: make the functions of RecurrenceEvaluator as static …
ivywei0125 May 22, 2024
bcc13df
address comment: move the validations to each property file
ivywei0125 May 23, 2024
89ed092
add test case in FeatureManagerTest
ivywei0125 May 23, 2024
57042e4
address comment: simplify code
ivywei0125 May 28, 2024
840b1af
address comment: throw exceptions directly
ivywei0125 May 28, 2024
5151259
address comment: move the related logic to the function to improve th…
ivywei0125 May 28, 2024
822f432
address comment: make as static method
ivywei0125 May 28, 2024
9b6199f
address comment: extract variables to avoid multi caller
ivywei0125 May 28, 2024
e703d7d
address comment: remove `convertToWeekDayNumber` method, no need any …
ivywei0125 May 29, 2024
bd614bb
address comment: simplify code
ivywei0125 May 29, 2024
c4138c5
address comment: remove unused code
ivywei0125 May 29, 2024
567b5c7
address comment: rename symbol
ivywei0125 May 29, 2024
65b98e6
address comment: add invalid settings check. Start and End must both …
ivywei0125 May 29, 2024
f5017a9
address comment: code style
ivywei0125 May 29, 2024
a52e3f7
address comment: add more test cases
ivywei0125 May 29, 2024
d6d815c
address comment: removed unused method
ivywei0125 May 30, 2024
2b928c9
address comment: update symbols
ivywei0125 May 30, 2024
59128ac
address comment: adjust spaces
ivywei0125 May 30, 2024
6ebe56c
address comment: add more test case
ivywei0125 May 30, 2024
dc659ad
address comment: update the README.md
ivywei0125 May 30, 2024
631b357
address comment: code style
ivywei0125 May 30, 2024
80c6011
address comment: typo error fix
ivywei0125 May 30, 2024
81da607
address comment: typo error fix
ivywei0125 May 30, 2024
e1c6007
address comment: update README.md
ivywei0125 May 30, 2024
f2ca9c7
address comment: update README.md
ivywei0125 May 30, 2024
22059be
Update sdk/spring/spring-cloud-azure-feature-management/README.md
ivywei0125 Jun 5, 2024
9e7f6a9
Update sdk/spring/spring-cloud-azure-feature-management/README.md
ivywei0125 Jun 5, 2024
59067dc
address comment: fix error in yaml example
ivywei0125 Jun 5, 2024
155074d
address comment: update README.md
ivywei0125 Jun 5, 2024
b59ae50
Update sdk/spring/spring-cloud-azure-feature-management/src/main/java…
ivywei0125 Jun 5, 2024
b68eb82
address comment: add @throws to java doc
ivywei0125 Jun 5, 2024
d957a1f
address comment: add "ignoreUnknown" annotation
ivywei0125 Jun 5, 2024
bad1e78
address comment: add year info to make it clearer
ivywei0125 Jun 5, 2024
cc86041
Merge branch 'yuwe/recurring-time-window-filter' of https://github.co…
ivywei0125 Jun 5, 2024
31c3bf7
address comment: add test case for time with RFC format
ivywei0125 Jun 5, 2024
a7c0271
address comment: fix build error
ivywei0125 Jun 6, 2024
f04780b
address comment: update the test case. Should use `assertThrows` inst…
ivywei0125 Jun 6, 2024
c5a31ff
address comment: update the error message to make it clearer
ivywei0125 Jun 13, 2024
688f252
update README file
ivywei0125 Jun 14, 2024
5bc512a
log error and then throw exception
ivywei0125 Jun 14, 2024
bc7da7d
address comment: update to support handler lower camel case
ivywei0125 Jun 17, 2024
d2d79f1
address comment: update code style
ivywei0125 Jun 17, 2024
3db57fb
address comment: update to use "Microsoft.TimeWindow"
ivywei0125 Jun 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 170 additions & 7 deletions sdk/spring/spring-cloud-azure-feature-management/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ feature-management:
feature-v:
enabled-for:
-
name: TimeWindowFilter
name: Microsoft.TimeWindow
parameters:
time-window-filter-setting-start: "Wed, 01 May 2019 13:59:59 GMT"
time-window-filter-setting-end: "Mon, 01 July 2019 00:00:00 GMT"
start: "Wed, 01 May 2019 13:59:59 GMT"
end: "Mon, 01 July 2019 00:00:00 GMT"
feature-w:
evaluate: false
enabled-for:
Expand Down Expand Up @@ -182,20 +182,183 @@ feature-management:

### TimeWindowFilter

This filter provides the capability to enable a feature based on a time window. If only `time-window-filter-setting-end` is specified, the feature will be considered on until that time. If only start is specified, the feature will be considered on at all points after that time. If both are specified the feature will be considered valid between the two times.
This filter provides the capability to enable a feature based on a time window. If only `end` is specified, the feature will be considered enabled until that time. If only `start` is specified, the feature will be considered enabled at all points after that time. If both are specified the feature will be considered enabled between the two times.

```yaml
feature-management:
feature-flags:
feature-v:
enabled-for:
-
name: TimeWindowFilter
name: Microsoft.TimeWindow
parameters:
time-window-filter-setting-start: "Wed, 01 May 2019 13:59:59 GMT",
time-window-filter-setting-end: "Mon, 01 July 2019 00:00:00 GMT"
start: "Wed, 01 May 2019 13:59:59 GMT"
end: "Mon, 01 July 2019 00:00:00 GMT"
```

The time window can be configured to recur periodically. This can be useful in the scenario where you have to enable/disable a feature during low or high traffic periods of a day or for certain days of the week. To expand the individual time window to recurring time windows, the recurrence rule should be specified in the `recurrence` parameter.


```yaml
feature-management:
feature-flags:
feature-v:
enabled-for:
-
name: Microsoft.TimeWindow
parameters:
start: "Fri, 22 Mar 2024 20:00:00 GMT"
end: "Sat, 23 Mar 2024 02:00:00 GMT"
recurrence:
pattern:
type: "Daily"
interval: 1
range:
type: "Numbered"
numberOfOccurrences: 3
```
To create a recurrence rule, you need to specify 3 parts: `start`, `end` and `recurrence`.
The `start` and `end` parameters define the time window which need to recur periodically.
The `recurrence` parameter is made up of two parts: `pattern` (how often the time window will repeat) and `range` (for how long the recurrence pattern will repeat). To create a recurrence rule, you must specify both `pattern` and `range`. Any pattern type can work with any range type.
The time zone offset of the `start` property will apply to the recurrence settings.

**Note:** `start` must be a valid first occurrence which fits the recurrence pattern. For example, if we define to repeat on every other Monday and Tuesday, then the start time should be in Monday or Tuesday. <br /> Additionally, the duration of the time window cannot be longer than how frequently it occurs. For example, it is invalid to have a 25-hour time window recur every day.

#### Recurrence Pattern

There are two possible recurrence pattern types: `Daily` and `Weekly`.

`Daily` causes an event to repeat based on a number of days between each occurrence. For example, "every day" or "every 3 days".

`Weekly` causes an event to repeat on the same day or days of the week, based on the number of weeks between each set of occurrences. For example, "every Monday" or "every other Friday".

* Parameters

Property | Relevance | Description
-----------|-------|-----
**type** | Required | `Daily`/`Weekly`.
**interval** | Optional | Specifies the interval between each start time of occurrence. Default value is 1. <br/>For example, if the interval is 2 with `Daily` type, and current occurrence is 2:00 AM ~ 3:00 AM on 2024/05/11, then the next occurrence should be 2:00 AM ~ 3:00 AM on 2024/05/13
**daysOfWeek** | Required/Optional | Specifies on which day(s) of the week the event occurs. It's required when `Weekly` type, negatively for `Daily` type.
**firstDayOfWeek** | Optional | Specifies which day is considered the first day of the week. Default value is `Sunday`. Negatively for `Daily` type.

* Example
* Daily

The following example will repeat from 2:00 AM to 3:00 AM on every 2 days

```yaml
start: "Mon, 13 May 2024 02:00:00 GMT"
end: "Mon, 13 May 2024 03:00:00 GMT"
recurrence:
pattern:
type: "Daily"
interval: 2
range:
type: "NoEnd"
```

* Weekly

The following example will repeat from 2:00 AM to 3:00 AM on every other Monday and Tuesday

```yaml
start: "Mon, 13 May 2024 02:00:00 GMT"
end: "Mon, 13 May 2024 03:00:00 GMT"
recurrence:
pattern:
type: "Weekly"
interval: 2
daysOfWeek:
- Monday
- Tuesday
range:
type: "NoEnd"
```
The following example shows a time window starts on one day and ends on another, repeat every week.

```yaml
start: "Mon, 13 May 2024 02:00:00 GMT"
end: "Mon, 14 May 2024 03:00:00 GMT"
recurrence:
pattern:
type: "Weekly"
interval: 1
daysOfWeek:
- Monday
range:
type: "NoEnd"
```


#### Recurrence Range

There are three possible recurrence range type: `NoEnd`, `EndDate` and `Numbered`.

* Parameter

Property | Relevance | Description |
-----------|---------------|-------------
**type** | Required | `NoEnd`/`EndDate`/`Numbered`.
**endDate** | Required/Optional | Specifies the date time to stop applying the pattern. Note that as long as the start time of the last occurrence falls before the end date, the end time of that occurrence is allowed to extend beyond it. <br /> It's required for `EndDate` type, negatively for `NoEnd` and `Numbered` type.
**NumberOfOccurrences** | Required/Optional | Specifies the number of days that it will occur. <br /> It's required for `Numbered` type, negatively for `NoEnd` and `EndDate` type.

* Example
* `NoEnd`

The `NoEnd` range causes the recurrence to occur indefinitely.

The following example will repeat from 6:00 PM to 8:00 PM every day.

``` yaml
start: "Fri, 22 Mar 2024 18:00:00 GMT"
end: "Fri, 22 Mar 2024 20:00:00 GMT"
recurrence:
pattern:
type: "Daily"
interval: 1
range:
type: "NoEnd"
```

* `EndDate`

The `EndDate` range causes the time window to occur on all days that fit the applicable pattern until the end date.

The following example will repeat from 6:00 PM to 8:00 PM every day until the last occurrence happens on April 1st, 2024.

``` yaml
start: "Fri, 22 Mar 2024 18:00:00 GMT"
end: "Fri, 22 Mar 2024 20:00:00 GMT"
recurrence:
pattern:
type: "Daily"
interval: 1
range:
type: "EndDate"
endDate: "Mon, 1 Apr 2024 20:00:00 GMT"
```

* `Numbered`

The `Numbered` range causes the time window to occur a fixed number of days (based on the pattern).

The following example will repeat from 6:00 PM to 8:00 PM on Monday and Tuesday until the there are 3 occurrences, which respectively happens on 2024/04/01(Mon), 2024/04/02(Tue) and 2024/04/08(Mon).

``` yaml
start: "Mon, 1 Apr 2024 18:00:00 GMT"
end: "Mon, 1 Apr 2024 20:00:00 GMT"
recurrence:
pattern:
type: "Weekly"
interval: 1
daysOfWeek:
- Monday
- Tuesday
range:
type: "Numbered"
numberOfOccurrences: 3
```

### TargetingFilter

This filter provides the capability to enable a feature for a target audience. An in-depth explanation of targeting is explained in the targeting section below. The filter parameters include an audience object which describes users, groups, and a default percentage of the user base that should have access to the feature, and an exclusion object for users and groups that should never be targeted. Each group object that is listed in the target audience must also specify what percentage of the group's members should have access. If a user is specified in the users section directly, or if the user is in the included percentage of any of the group rollouts, or if the user falls into the default rollout percentage then that user will have the feature enabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;

import com.azure.spring.cloud.feature.management.implementation.FeatureFilterUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
Expand Down Expand Up @@ -84,7 +83,7 @@ public class TargetingFilter implements FeatureFilter {

/**
* Filter for targeting a user/group/percentage of users.
*
*
* @param contextAccessor Accessor for identifying the current user/group when evaluating
*/
public TargetingFilter(TargetingContextAccessor contextAccessor) {
Expand All @@ -94,7 +93,7 @@ public TargetingFilter(TargetingContextAccessor contextAccessor) {

/**
* `Microsoft.TargetingFilter` evaluates a user/group/overall rollout of a feature.
*
*
* @param contextAccessor Context for evaluating the users/groups.
* @param options enables customization of the filter.
*/
Expand Down Expand Up @@ -126,13 +125,13 @@ public boolean evaluate(FeatureFilterEvaluationContext context) {
parameters = (Map<String, Object>) audienceObject;
}

updateValueFromMapToList(parameters, USERS);
updateValueFromMapToList(parameters, GROUPS);
FeatureFilterUtils.updateValueFromMapToList(parameters, USERS);
FeatureFilterUtils.updateValueFromMapToList(parameters, GROUPS);

Audience audience;
String exclusionValue = getKeyCase(parameters, EXCLUSION_CAMEL);
String exclusionUserValue = getKeyCase((Map<String, Object>) parameters.get(exclusionValue), "Users");
String exclusionGroupsValue = getKeyCase((Map<String, Object>) parameters.get(exclusionValue), "Groups");
String exclusionValue = FeatureFilterUtils.getKeyCase(parameters, EXCLUSION_CAMEL);
String exclusionUserValue = FeatureFilterUtils.getKeyCase((Map<String, Object>) parameters.get(exclusionValue), "Users");
String exclusionGroupsValue = FeatureFilterUtils.getKeyCase((Map<String, Object>) parameters.get(exclusionValue), "Groups");

if (((Map<String, Object>) parameters.getOrDefault(exclusionValue, new HashMap<>()))
.get(exclusionUserValue) instanceof List) {
Expand Down Expand Up @@ -194,13 +193,6 @@ public boolean evaluate(FeatureFilterEvaluationContext context) {
return isTargeted(defaultContextId, audience.getDefaultRolloutPercentage());
}

private String getKeyCase(Map<String, Object> parameters, String key) {
if (parameters != null && parameters.containsKey(key)) {
return key;
}
return key.toLowerCase(Locale.getDefault());
}

private boolean targetUser(String userId, List<String> users) {
return userId != null && users != null && users.stream().anyMatch(user -> equals(userId, user));
}
Expand Down Expand Up @@ -234,7 +226,7 @@ private boolean validateTargetingContext(TargetingFilterContext targetingContext

/**
* Computes the percentage that the contextId falls into.
*
*
* @param contextId Id of the context being targeted
* @return the bucket value of the context id
* @throws TargetingException Unable to create hash of target context
Expand Down Expand Up @@ -265,8 +257,8 @@ private boolean isTargeted(String contextId, double percentage) {

/**
* Validates the settings of a targeting filter.
*
* @param settings targeting filter settings
*
* @param audience targeting filter settings
* @throws TargetingException when a required parameter is missing or percentage value is greater than 100.
*/
void validateSettings(Audience audience) {
Expand Down Expand Up @@ -303,7 +295,7 @@ void validateSettings(Audience audience) {

/**
* Checks if two strings are equal, ignores case if configured to.
*
*
* @param s1 string to compare
* @param s2 string to compare
* @return true if the strings are equal
Expand All @@ -314,21 +306,4 @@ private boolean equals(String s1, String s2) {
}
return s1.equals(s2);
}

/**
* Looks at the given key in the parameters and coverts it to a list if it is currently a map. Used for updating
* fields in the targeting filter.
*
* @param <T> Type of object inside of parameters for the given key
* @param parameters map of generic objects
* @param key key of object int the parameters map
*/
@SuppressWarnings("unchecked")
private void updateValueFromMapToList(Map<String, Object> parameters, String key) {
Object objectMap = parameters.get(key);
if (objectMap instanceof Map) {
Collection<Object> toType = ((Map<String, Object>) objectMap).values();
parameters.put(key, toType);
}
}
}
Loading