Skip to content

Commit 397933d

Browse files
authored
Merge pull request #444 from microsoft/main
Merge main into release v3
2 parents d126b84 + f9beae5 commit 397933d

27 files changed

+3090
-59
lines changed

README.md

+142-6
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ The feature management library supports appsettings.json as a feature flag sourc
8080
"Name": "TimeWindow",
8181
"Parameters": {
8282
"Start": "Wed, 01 May 2019 13:59:59 GMT",
83-
"End": "Mon, 01 July 2019 00:00:00 GMT"
83+
"End": "Mon, 01 Jul 2019 00:00:00 GMT"
8484
}
8585
}
8686
]
@@ -131,7 +131,7 @@ A `RequirementType` of `All` changes the traversal. First, if there are no filte
131131
"Name": "TimeWindow",
132132
"Parameters": {
133133
"Start": "Mon, 01 May 2023 13:59:59 GMT",
134-
"End": "Sat, 01 July 2023 00:00:00 GMT"
134+
"End": "Sat, 01 Jul 2023 00:00:00 GMT"
135135
}
136136
},
137137
{
@@ -163,7 +163,7 @@ The feature management library also supports the usage of the [`Microsoft Featur
163163
"name": "Microsoft.TimeWindow",
164164
"parameters": {
165165
"Start": "Mon, 01 May 2023 13:59:59 GMT",
166-
"End": "Sat, 01 July 2023 00:00:00 GMT"
166+
"End": "Sat, 01 Jul 2023 00:00:00 GMT"
167167
}
168168
}
169169
]
@@ -565,13 +565,149 @@ This filter provides the capability to enable a feature based on a time window.
565565
"Name": "Microsoft.TimeWindow",
566566
"Parameters": {
567567
"Start": "Wed, 01 May 2019 13:59:59 GMT",
568-
"End": "Mon, 01 July 2019 00:00:00 GMT"
568+
"End": "Mon, 01 Jul 2019 00:00:00 GMT"
569569
}
570570
}
571571
]
572572
}
573573
```
574574

575+
The time window can be configured to recur periodically. This can be useful for the scenarios where one may need to turn on a feature during a low or high traffic period of a day or certain days of a week. To expand the individual time window to recurring time windows, the recurrence rule should be specified in the `Recurrence` parameter.
576+
577+
**Note:** `Start` and `End` must be both specified to enable `Recurrence`.
578+
579+
``` JavaScript
580+
"EnhancedPipeline": {
581+
"EnabledFor": [
582+
{
583+
"Name": "Microsoft.TimeWindow",
584+
"Parameters": {
585+
"Start": "Fri, 22 Mar 2024 20:00:00 GMT",
586+
"End": "Sat, 23 Mar 2024 02:00:00 GMT",
587+
"Recurrence": {
588+
"Pattern": {
589+
"Type": "Daily",
590+
"Interval": 1
591+
},
592+
"Range": {
593+
"Type": "NoEnd"
594+
}
595+
}
596+
}
597+
}
598+
]
599+
}
600+
```
601+
602+
The `Recurrence` settings is made up of two parts: `Pattern` (how often the time window will repeat) and `Range` (for how long the recurrence pattern will repeat).
603+
604+
#### Recurrence Pattern
605+
606+
There are two possible recurrence pattern types: `Daily` and `Weekly`. For example, a time window could repeat "every day", "every 3 days", "every Monday" or "on Friday per 2 weeks".
607+
608+
Depending on the type, certain fields of the `Pattern` are required, optional, or ignored.
609+
610+
- `Daily`
611+
612+
The daily recurrence pattern causes the time window to repeat based on a number of days between each occurrence.
613+
614+
| Property | Relevance | Description |
615+
|----------|-----------|-------------|
616+
| **Type** | Required | Must be set to `Daily`. |
617+
| **Interval** | Optional | Specifies the number of days between each occurrence. Default value is 1. |
618+
619+
- `Weekly`
620+
621+
The weekly recurrence pattern causes the time window to repeat on the same day or days of the week, based on the number of weeks between each set of occurrences.
622+
623+
| Property | Relevance | Description |
624+
|----------|-----------|-------------|
625+
| **Type** | Required | Must be set to `Weekly`. |
626+
| **DaysOfWeek** | Required | Specifies on which day(s) of the week the event occurs. |
627+
| **Interval** | Optional | Specifies the number of weeks between each set of occurrences. Default value is 1. |
628+
| **FirstDayOfWeek** | Optional | Specifies which day is considered the first day of the week. Default value is `Sunday`. |
629+
630+
The following example will repeat the time window every other Monday and Tuesday
631+
632+
``` javascript
633+
"Pattern": {
634+
"Type": "Weekly",
635+
"Interval": 2,
636+
"DaysOfWeek": ["Monday", "Tuesday"]
637+
}
638+
```
639+
640+
**Note:** `Start` must be a valid first occurrence which fits the recurrence pattern. 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.
641+
642+
#### Recurrence Range
643+
644+
There are three possible recurrence range type: `NoEnd`, `EndDate` and `Numbered`.
645+
646+
- `NoEnd`
647+
648+
The `NoEnd` range causes the recurrence to occur indefinitely.
649+
650+
| Property | Relevance | Description |
651+
|----------|-----------|-------------|
652+
| **Type** | Required | Must be set to `NoEnd`. |
653+
654+
- `EndDate`
655+
656+
The `EndDate` range causes the time window to occur on all days that fit the applicable pattern until the end date.
657+
658+
| Property | Relevance | Description |
659+
|----------|-----------|-------------|
660+
| **Type** | Required | Must be set to `EndDate`. |
661+
| **EndDate** | Required | 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. |
662+
663+
The following example will repeat the time window every day until the last occurrence happens on April 1st, 2024.
664+
665+
``` javascript
666+
"Start": "Fri, 22 Mar 2024 18:00:00 GMT",
667+
"End": "Fri, 22 Mar 2024 20:00:00 GMT",
668+
"Recurrence":{
669+
"Pattern": {
670+
"Type": "Daily",
671+
"Interval": 1
672+
},
673+
"Range": {
674+
"Type": "EndDate",
675+
"EndDate": "Mon, 1 Apr 2024 20:00:00 GMT"
676+
}
677+
}
678+
```
679+
680+
- `Numbered`
681+
682+
The `Numbered` range causes the time window to occur a fixed number of times (based on the pattern).
683+
684+
| Property | Relevance | Description |
685+
|----------|-----------|-------------|
686+
| **Type** | Required | Must be set to `Numbered`. |
687+
| **NumberOfOccurrences** | Required | Specifies the number of occurrences. |
688+
689+
The following example will repeat the time window on Monday and Tuesday until the there are 3 occurrences, which respectively happens on April 1st(Mon), April 2nd(Tue) and April 8th(Mon).
690+
691+
``` javascript
692+
"Start": "Mon, 1 Apr 2024 18:00:00 GMT",
693+
"End": "Mon, 1 Apr 2024 20:00:00 GMT",
694+
"Recurrence":{
695+
"Pattern": {
696+
"Type": "Weekly",
697+
"Interval": 1,
698+
"DaysOfWeek": ["Monday", "Tuesday"]
699+
},
700+
"Range": {
701+
"Type": "Numbered",
702+
"NumberOfOccurrences": 3
703+
}
704+
}
705+
```
706+
707+
To create a recurrence rule, you must specify both `Pattern` and `Range`. Any pattern type can work with any range type.
708+
709+
**Advanced:** The time zone offset of the `Start` property will apply to the recurrence settings.
710+
575711
### Microsoft.Targeting
576712

577713
This filter provides the capability to enable a feature for a target audience. An in-depth explanation of targeting is explained in the [targeting](./README.md#Targeting) section below. The filter parameters include an audience object which describes users, groups, excluded users/groups, and a default percentage of the user base that should have access to the feature. 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 exclusion section, either directly or if the user is in an excluded group, the feature will be disabled. Otherwise, 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.
@@ -666,8 +802,8 @@ IFeatureManager fm;
666802
TargetingContext targetingContext = new TargetingContext
667803
{
668804
UserId = userId,
669-
Groups = groups;
670-
}
805+
Groups = groups
806+
};
671807
672808
await fm.IsEnabledAsync(featureName, targetingContext);
673809
```

src/Microsoft.FeatureManagement.AspNetCore/FeatureGateAttribute.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,12 @@ public FeatureGateAttribute(RequirementType requirementType, params object[] fea
9090
public IEnumerable<string> Features { get; }
9191

9292
/// <summary>
93-
/// Controls whether any or all features in <see cref="Features"/> should be enabled to pass.
93+
/// Controls whether any or all features in <see cref="FeatureGateAttribute.Features"/> should be enabled to pass.
9494
/// </summary>
9595
public RequirementType RequirementType { get; }
9696

9797
/// <summary>
98-
/// Performs controller action pre-procesing to ensure that at least one of the specified features are enabled.
98+
/// Performs controller action pre-procesing to ensure that any or all of the specified features are enabled.
9999
/// </summary>
100100
/// <param name="context">The context of the MVC action.</param>
101101
/// <param name="next">The action delegate.</param>

src/Microsoft.FeatureManagement.AspNetCore/Microsoft.FeatureManagement.AspNetCore.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<!-- Official Version -->
55
<PropertyGroup>
66
<MajorVersion>3</MajorVersion>
7-
<MinorVersion>2</MinorVersion>
7+
<MinorVersion>3</MinorVersion>
88
<PatchVersion>0</PatchVersion>
99
</PropertyGroup>
1010

src/Microsoft.FeatureManagement/ConfigurationFeatureDefinitionProvider.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,14 @@ public async IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()
133133

134134
//
135135
// Underlying IConfigurationSection data is dynamic so latest feature definitions are returned
136-
yield return _definitions.GetOrAdd(featureName, (_) => ReadFeatureDefinition(featureSection));
136+
FeatureDefinition definition = _definitions.GetOrAdd(featureName, (_) => ReadFeatureDefinition(featureSection));
137+
138+
//
139+
// Null cache entry possible if someone accesses non-existent flag directly (IsEnabled)
140+
if (definition != null)
141+
{
142+
yield return definition;
143+
}
137144
}
138145
}
139146

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
5+
using System;
6+
7+
namespace Microsoft.FeatureManagement.FeatureFilters
8+
{
9+
/// <summary>
10+
/// Abstracts the system clock to facilitate testing.
11+
/// .NET8 offers an abstract class TimeProvider. After we stop supporting .NET version less than .NET8, this ISystemClock should retire.
12+
/// </summary>
13+
internal interface ISystemClock
14+
{
15+
/// <summary>
16+
/// Retrieves the current system time in UTC.
17+
/// </summary>
18+
public DateTimeOffset UtcNow { get; }
19+
}
20+
}

src/Microsoft.FeatureManagement/FeatureFilters/PercentageFilter.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using Microsoft.Extensions.Configuration;
55
using Microsoft.Extensions.Logging;
66
using Microsoft.FeatureManagement.Utils;
7+
using System;
78
using System.Threading.Tasks;
89

910
namespace Microsoft.FeatureManagement.FeatureFilters
@@ -21,9 +22,9 @@ public class PercentageFilter : IFeatureFilter, IFilterParametersBinder
2122
/// Creates a percentage based feature filter.
2223
/// </summary>
2324
/// <param name="loggerFactory">A logger factory for creating loggers.</param>
24-
public PercentageFilter(ILoggerFactory loggerFactory)
25+
public PercentageFilter(ILoggerFactory loggerFactory = null)
2526
{
26-
_logger = loggerFactory.CreateLogger<PercentageFilter>();
27+
_logger = loggerFactory?.CreateLogger<PercentageFilter>();
2728
}
2829

2930
/// <summary>
@@ -43,6 +44,11 @@ public object BindParameters(IConfiguration filterParameters)
4344
/// <returns>True if the feature is enabled, false otherwise.</returns>
4445
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
4546
{
47+
if (context == null)
48+
{
49+
throw new ArgumentNullException(nameof(context));
50+
}
51+
4652
//
4753
// Check if prebound settings available, otherwise bind from parameters.
4854
PercentageFilterSettings settings = (PercentageFilterSettings)context.Settings ?? (PercentageFilterSettings)BindParameters(context.Parameters);
@@ -51,7 +57,7 @@ public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
5157

5258
if (settings.Value < 0)
5359
{
54-
_logger.LogWarning($"The '{Alias}' feature filter does not have a valid '{nameof(settings.Value)}' value for feature '{context.FeatureName}'");
60+
_logger?.LogWarning($"The '{Alias}' feature filter does not have a valid '{nameof(settings.Value)}' value for feature '{context.FeatureName}'");
5561

5662
result = false;
5763
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
//
4+
namespace Microsoft.FeatureManagement.FeatureFilters
5+
{
6+
/// <summary>
7+
/// A recurrence definition describing how time window recurs
8+
/// </summary>
9+
public class Recurrence
10+
{
11+
/// <summary>
12+
/// The recurrence pattern specifying how often the time window repeats
13+
/// </summary>
14+
public RecurrencePattern Pattern { get; set; }
15+
16+
/// <summary>
17+
/// The recurrence range specifying how long the recurrence pattern repeats
18+
/// </summary>
19+
public RecurrenceRange Range { get; set; }
20+
}
21+
}

0 commit comments

Comments
 (0)