Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support recurrence for TimeWindowFilter #266

Merged
merged 63 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
92e8a08
improvement & target on main branch
zhiyuanliang-ms Nov 14, 2023
a732a24
remove duplicated null check
zhiyuanliang-ms Nov 14, 2023
290e96c
remove redundant if
zhiyuanliang-ms Nov 14, 2023
0e3e1e4
improvement
zhiyuanliang-ms Nov 15, 2023
353c025
Merge branch 'main' into zhiyuanliang/recurring-time-window
zhiyuanliang-ms Nov 20, 2023
3cdcc56
resolve comments
zhiyuanliang-ms Nov 20, 2023
9600e7a
fix typo
zhiyuanliang-ms Dec 11, 2023
64904ec
update
zhiyuanliang-ms Dec 11, 2023
7bd2844
Merge branch 'main' into zhiyuanliang/recurring-time-window
zhiyuanliang-ms Dec 11, 2023
bf2e2eb
use enum
zhiyuanliang-ms Jan 15, 2024
3041b06
Update testcases
zhiyuanliang-ms Jan 16, 2024
ba89364
fix bug
zhiyuanliang-ms Jan 16, 2024
1403934
Merge branch 'main' into zhiyuanliang/recurring-time-window
zhiyuanliang-ms Jan 16, 2024
d8c1790
update
zhiyuanliang-ms Jan 18, 2024
e2b3d84
update the logic of FindWeeklyPreviousOccurrence
zhiyuanliang-ms Jan 18, 2024
e2fccc2
fix bug
zhiyuanliang-ms Jan 18, 2024
25ff14f
add comments
zhiyuanliang-ms Jan 18, 2024
bee8718
update
zhiyuanliang-ms Jan 18, 2024
ce5dfae
fix bug & add testcases
zhiyuanliang-ms Jan 18, 2024
724292b
update
zhiyuanliang-ms Jan 18, 2024
a3b8fa4
Merge branch 'main' into zhiyuanliang/recurring-time-window
zhiyuanliang-ms Jan 18, 2024
63c7343
update comment
zhiyuanliang-ms Jan 18, 2024
d78d844
test
zhiyuanliang-ms Jan 18, 2024
2136562
update comments
zhiyuanliang-ms Jan 22, 2024
c0ab54c
Merge branch 'zhiyuanliang/recurring-time-window' of https://github.c…
zhiyuanliang-ms Jan 22, 2024
ab9961c
update
zhiyuanliang-ms Jan 23, 2024
252b5c3
update
zhiyuanliang-ms Jan 23, 2024
9fc5bcc
add testcase
zhiyuanliang-ms Jan 24, 2024
b9f5ab2
Merge branch 'main' into zhiyuanliang/recurring-time-window
zhiyuanliang-ms Jan 29, 2024
29efb5d
remove monthly/yearly recurrence pattern
zhiyuanliang-ms Jan 29, 2024
421ec48
do not mention monthly and yearly pattern
zhiyuanliang-ms Feb 10, 2024
4eca45d
Merge branch 'main' of https://github.com/microsoft/FeatureManagement…
zhiyuanliang-ms Feb 10, 2024
e78a0f6
add more comments
zhiyuanliang-ms Feb 20, 2024
b7eb586
update the algorithm to find weekly previous occurrence
zhiyuanliang-ms Feb 20, 2024
c653198
update
zhiyuanliang-ms Feb 20, 2024
f9537d4
fix typo
zhiyuanliang-ms Feb 20, 2024
9c7ec7d
update
zhiyuanliang-ms Feb 20, 2024
2187121
rename variable
zhiyuanliang-ms Feb 20, 2024
fd098f1
Merge branch 'main' of https://github.com/microsoft/FeatureManagement…
zhiyuanliang-ms Feb 26, 2024
934784c
cache added & do validation for only once
zhiyuanliang-ms Feb 28, 2024
77b24b1
add comments
zhiyuanliang-ms Feb 28, 2024
f1eefe9
add more testcases
zhiyuanliang-ms Feb 29, 2024
0645a25
add more test
zhiyuanliang-ms Feb 29, 2024
acdc16b
not include the end of a time window
zhiyuanliang-ms Mar 7, 2024
ace075c
move recurrence validation to RecurrenceValidator
zhiyuanliang-ms Mar 20, 2024
096d136
resolve conflicts
zhiyuanliang-ms Mar 20, 2024
856e183
README updated
zhiyuanliang-ms Mar 22, 2024
b1c06ad
update readme
zhiyuanliang-ms Apr 7, 2024
0bb8017
update CalculateSurroundingOccurrences method
zhiyuanliang-ms Apr 7, 2024
b5b37f0
add CalculateClosestStart method
zhiyuanliang-ms Apr 9, 2024
4fa9002
testcase updated
zhiyuanliang-ms Apr 9, 2024
6196e21
update
zhiyuanliang-ms Apr 10, 2024
188ce44
use ISystemClock for testing & add limit on time window duration
zhiyuanliang-ms Apr 10, 2024
cd94d26
add testcase for timezone
zhiyuanliang-ms Apr 10, 2024
6abaa0d
update
zhiyuanliang-ms Apr 11, 2024
341f78f
update comments
zhiyuanliang-ms Apr 15, 2024
ac8f8f4
Merge branch 'main' into zhiyuanliang/recurring-time-window
zhiyuanliang-ms Apr 15, 2024
39149c6
Merge branch 'main' of https://github.com/microsoft/FeatureManagement…
zhiyuanliang-ms Apr 17, 2024
1a04153
change method type
zhiyuanliang-ms Apr 22, 2024
8e9f893
remove unused reference
zhiyuanliang-ms Apr 22, 2024
7deda05
rename variable
zhiyuanliang-ms Apr 22, 2024
bf992ff
remove used reference
zhiyuanliang-ms Apr 22, 2024
e1616a6
remove empty lines
zhiyuanliang-ms Apr 22, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
namespace Microsoft.FeatureManagement.FeatureFilters
{
/// <summary>
/// A recurrence definition describing how time window recurs
/// </summary>
public class Recurrence
{
/// <summary>
/// The recurrence pattern specifying how often the time window repeats
/// </summary>
public RecurrencePattern Pattern { get; set; }

/// <summary>
/// The recurrence range specifying how long the recurrence pattern repeats
/// </summary>
public RecurrenceRange Range { get; set; }
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System.Collections.Generic;

namespace Microsoft.FeatureManagement.FeatureFilters
{
/// <summary>
/// The recurrence pattern specifying how often the time window repeats
/// </summary>
public class RecurrencePattern
{
/// <summary>
/// The recurrence pattern type
/// </summary>
public string Type { get; set; }
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// The number of units between occurrences, where units can be in days, weeks, months, or years, depending on the pattern type
/// </summary>
public int Interval { get; set; } = 1;
jimmyca15 marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// The days of the week on which the time window occurs
/// </summary>
public IEnumerable<string> DaysOfWeek { get; set; }

/// <summary>
/// The first day of the week.
/// </summary>
public string FirstDayOfWeek { get; set; } = "Sunday";
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Specifies on which instance of the allowed days specified in DaysOfWeek the time window occurs, counted from the first instance in the month
/// </summary>
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved
public string Index { get; set; } = "First";
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// The day of the month on which the time window occurs
/// </summary>
public int? DayOfMonth { get; set; }

/// <summary>
/// The month on which the time window occurs
/// </summary>
public int? Month { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
using System;

namespace Microsoft.FeatureManagement.FeatureFilters
{
/// <summary>
/// The recurrence range specifying how long the recurrence pattern repeats
/// </summary>
public class RecurrenceRange
{
/// <summary>
/// The recurrence range type
/// </summary>
public string Type { get; set; } = "NoEnd";
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// The date to stop applying the recurrence pattern
/// </summary>
public DateTimeOffset? EndDate { get; set; }
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// The number of times to repeat the time window
/// </summary>
public int? NumberOfOccurrences { get; set; }

/// <summary>
/// Time zone for recurrence settings, e.g. UTC+08:00
/// </summary>
public string RecurrenceTimeZone { get; set; }
}
}
19 changes: 16 additions & 3 deletions src/Microsoft.FeatureManagement/FeatureFilters/TimeWindowFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ namespace Microsoft.FeatureManagement.FeatureFilters
{
/// <summary>
/// A feature filter that can be used to activate a feature based on a time window.
/// The time window filter supports recurrence settings. The time window can occur repeatedly.
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
[FilterAlias(Alias)]
public class TimeWindowFilter : IFeatureFilter, IFilterParametersBinder
Expand All @@ -23,7 +24,7 @@ public class TimeWindowFilter : IFeatureFilter, IFilterParametersBinder
/// <param name="loggerFactory">A logger factory for creating loggers.</param>
public TimeWindowFilter(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<TimeWindowFilter>();
_logger = loggerFactory?.CreateLogger<TimeWindowFilter>() ?? throw new ArgumentNullException(nameof(loggerFactory));
}

/// <summary>
Expand All @@ -37,7 +38,7 @@ public object BindParameters(IConfiguration filterParameters)
}

/// <summary>
/// Evaluates whether a feature is enabled based on a configurable time window.
/// Evaluates whether a feature is enabled based on a configurable fixed time window or recurring time windows.
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <param name="context">The feature evaluation context.</param>
/// <returns>True if the feature is enabled, false otherwise.</returns>
Expand All @@ -56,7 +57,19 @@ public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
return Task.FromResult(false);
}

return Task.FromResult((!settings.Start.HasValue || now >= settings.Start.Value) && (!settings.End.HasValue || now < settings.End.Value));
//
// Hit the first occurrence of the time window
if ((!settings.Start.HasValue || now >= settings.Start.Value) && (!settings.End.HasValue || now < settings.End.Value))
{
return Task.FromResult(true);
}

if (settings.Recurrence != null)
{
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved
return Task.FromResult(RecurrenceEvaluator.MatchRecurrence(now, settings));
zhiyuanliang-ms marked this conversation as resolved.
Show resolved Hide resolved
}

return Task.FromResult(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@ public class TimeWindowFilterSettings
/// An optional start time used to determine when a feature configured to use the <see cref="TimeWindowFilter"/> feature filter should be enabled.
/// If no start time is specified the time window is considered to have already started.
/// </summary>
public DateTimeOffset? Start { get; set; } // E.g. "Wed, 01 May 2019 22:59:30 GMT"
public DateTimeOffset? Start { get; set; }

/// <summary>
/// An optional end time used to determine when a feature configured to use the <see cref="TimeWindowFilter"/> feature filter should be enabled.
/// If no end time is specified the time window is considered to never end.
/// </summary>
public DateTimeOffset? End { get; set; } // E.g. "Wed, 01 May 2019 23:00:00 GMT"
public DateTimeOffset? End { get; set; }

/// <summary>
/// Add-on recurrence rule allows the time window defined by Start and End to recur.
/// The rule specifies both how often the time window repeats and for how long.
/// </summary>
public Recurrence Recurrence { get; set; }
}
}
17 changes: 17 additions & 0 deletions tests/Tests.FeatureManagement/FeatureManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,8 @@ public async Task TimeWindow()
const string feature2 = "feature2";
const string feature3 = "feature3";
const string feature4 = "feature4";
const string feature5 = "feature5";
const string feature6 = "feature6";

Environment.SetEnvironmentVariable($"FeatureManagement:{feature1}:EnabledFor:0:Name", "TimeWindow");
Environment.SetEnvironmentVariable($"FeatureManagement:{feature1}:EnabledFor:0:Parameters:End", DateTimeOffset.UtcNow.AddDays(1).ToString("r"));
Expand All @@ -239,6 +241,19 @@ public async Task TimeWindow()
Environment.SetEnvironmentVariable($"FeatureManagement:{feature4}:EnabledFor:0:Name", "TimeWindow");
Environment.SetEnvironmentVariable($"FeatureManagement:{feature4}:EnabledFor:0:Parameters:Start", DateTimeOffset.UtcNow.AddDays(1).ToString("r"));

Environment.SetEnvironmentVariable($"FeatureManagement:{feature5}:EnabledFor:0:Name", "TimeWindow");
Environment.SetEnvironmentVariable($"FeatureManagement:{feature5}:EnabledFor:0:Parameters:Start", DateTimeOffset.UtcNow.AddDays(-2).ToString("r"));
Environment.SetEnvironmentVariable($"FeatureManagement:{feature5}:EnabledFor:0:Parameters:End", DateTimeOffset.UtcNow.AddDays(-1).ToString("r"));
Environment.SetEnvironmentVariable($"FeatureManagement:{feature5}:EnabledFor:0:Parameters:Recurrence:Pattern:Type", "Daily");
Environment.SetEnvironmentVariable($"FeatureManagement:{feature5}:EnabledFor:0:Parameters:Recurrence:Range:Type", "NoEnd");

Environment.SetEnvironmentVariable($"FeatureManagement:{feature6}:EnabledFor:0:Name", "TimeWindow");
Environment.SetEnvironmentVariable($"FeatureManagement:{feature6}:EnabledFor:0:Parameters:Start", DateTimeOffset.UtcNow.AddDays(-2).ToString("r"));
Environment.SetEnvironmentVariable($"FeatureManagement:{feature6}:EnabledFor:0:Parameters:End", DateTimeOffset.UtcNow.AddDays(-1).ToString("r"));
Environment.SetEnvironmentVariable($"FeatureManagement:{feature6}:EnabledFor:0:Parameters:Recurrence:Pattern:Type", "Daily");
Environment.SetEnvironmentVariable($"FeatureManagement:{feature6}:EnabledFor:0:Parameters:Recurrence:Pattern:Interval", "3");
Environment.SetEnvironmentVariable($"FeatureManagement:{feature6}:EnabledFor:0:Parameters:Recurrence:Range:Type", "NoEnd");

IConfiguration config = new ConfigurationBuilder().AddEnvironmentVariables().Build();

var serviceCollection = new ServiceCollection();
Expand All @@ -254,6 +269,8 @@ public async Task TimeWindow()
Assert.False(await featureManager.IsEnabledAsync(feature2));
Assert.True(await featureManager.IsEnabledAsync(feature3));
Assert.False(await featureManager.IsEnabledAsync(feature4));
Assert.True(await featureManager.IsEnabledAsync(feature5));
Assert.False(await featureManager.IsEnabledAsync(feature6));
}

[Fact]
Expand Down
Loading