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

App Configuration: recurring time window filter #40093

Open
wants to merge 52 commits into
base: feature/spring-boot-3
Choose a base branch
from

Conversation

ivywei0125
Copy link
Contributor

Add "Recurrence" parameter for TimeWindowFilter using Outlook-style schema:
https://learn.microsoft.com/en-us/graph/outlook-schedule-recurring-events#using-patterns-and-ranges-to-create-recurring-events

Example:

feature-management:
  feature-flags:
    feature-v:
      enabled-for:
        -
         name: TimeWindowFilter
          parameters:
            start: "Fri, 22 Mar 2024 20:00:00 GMT",
            end: "Mon, 01 July 2019 00:00:00 GMT",
            recurrence:
              pattern:
                type: "Daily",
                interval: 1
              range:
                type: "NoEnd"

The main logic of how to check whether current time is within any recurring time window:
Find the previous occurrence of the recurring time window, let's call it prevOccurrenceStart
Check whether the current time: time is within the time window: prevOccurrenceStart ~ prevOccurrenceStart + End - Start

@ivywei0125 ivywei0125 self-assigned this May 9, 2024
@ivywei0125 ivywei0125 changed the title Yuwe/recurring time window filter App Configuration: recurring time window filter May 9, 2024
@github-actions github-actions bot added the azure-spring All azure-spring related issues label May 9, 2024
@azure-sdk
Copy link
Collaborator

API change check

APIView has identified API level changes in this PR and created following API reviews.

com.azure.spring:spring-cloud-azure-feature-management

sdk/spring/spring-cloud-azure-feature-management/README.md Outdated Show resolved Hide resolved
sdk/spring/spring-cloud-azure-feature-management/README.md Outdated Show resolved Hide resolved
type: "Daily",
interval: 1
range:
type: "NoEnd"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is end needed if there is a range type that is "NoEnd"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using the recurrence pattern, we need to provide both start and end. The time window is end - start.
If the range type is "NoEnd", then we don't need to provide other paramters(endDate, numberOfOccurrences) in range scope.
For example, if we want to enable a feature flag in 2:00 AM ~3:00 AM every day from 2024/05/11. Then the config yaml should be:

start: "Sat, 11 May 2024 02:00:00 GMT",
end: "Sat, 11 May 2024 03:00:00 GMT",
recurrence:
  pattern:
    type: "Daily",
    interval: 1
  range:
    type: "NoEnd"

If we want to enable a feature flag in 2:00 AM ~3:00 AM every day from 2024/05/11 to 2024/06/11. Then the config yaml should be:

start: "Sat, 11 May 2024 02:00:00 GMT",
end: "Sat, 11 May 2024 03:00:00 GMT",
recurrence:
  pattern:
    type: "Daily",
    interval: 1
  range:
    type: "EndDate",
    endDate: "Tue, 11 June 2024 00:00:00 GMT"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the intro paragraph no longer works as it goes agains the recurring items. Maybe instead of what we have here where both are one section we make it 2 different sections. I know it's the same filter, but they don't operate the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But if we make it into 2 difference sections, then it's also confused. It looks like we have 2 fiters with same name "TimeWindowFilter", different parameters.
Another solution, how about updating the example, not using NoEnd, use Numbered instaed?

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

sdk/spring/spring-cloud-azure-feature-management/README.md Outdated Show resolved Hide resolved
sdk/spring/spring-cloud-azure-feature-management/README.md Outdated Show resolved Hide resolved
}

private boolean validateRecurrencePattern() {
if (!validateInterval()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we follow the Spring validation pattern these should be errors thrown and validation methods have void returns.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use void return


// Time window duration must be shorter than how frequently it occurs
final Duration intervalDuration = Duration.ofDays((long) pattern.getInterval() * RecurrenceConstants.DAYS_PER_WEEK);
final Duration timeWindowDuration = Duration.between(settings.getStart(), settings.getEnd());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should move this down to where it is validated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Move these logic to the validateTimeWindowDuration function.

try {
DayOfWeek.valueOf(dayOfWeek.toUpperCase());
} catch (IllegalArgumentException e) {
reason = RecurrenceConstants.UNRECOGNIZED_VALUE;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the reason we should be clearer that it is an invalid date of the week.

* @param firstDayOfWeek The first day of the week.
* @return True if the duration is compliant with days of week, false otherwise.
*/
private boolean isDurationCompliantWithDaysOfWeek(Duration duration, int interval, List<String> daysOfWeek, String firstDayOfWeek) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be verify, this method is checking to make sure if I have a time window open the first one closes before the next starts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This logic seems like it might not work around daylight savings time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this method isDurationCompliantWithDaysOfWeek is to make sure the first one close before the next.

type: "Daily",
interval: 1
range:
type: "NoEnd"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the intro paragraph no longer works as it goes agains the recurring items. Maybe instead of what we have here where both are one section we make it 2 different sections. I know it's the same filter, but they don't operate the same.

sdk/spring/spring-cloud-azure-feature-management/README.md Outdated Show resolved Hide resolved
public static final String OUT_OF_RANGE = "The value of parameter %s is out of the accepted range.";
public static final String UNRECOGNIZED_VALUE = "The value of parameter %s is unrecognizable.";
public static final String REQUIRED_PARAMETER = "Value cannot be null for required parameter: %s";
public static final String NOT_MATCHED = "%s date is not a valid first occurrence.";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if my understanding right. Usually the "%s" will be replaced with the parameter name.
So if there's a NOT_MATCHED error, then the error message will be "The start date Start is not a valid first occurrence. " Would it be repeated when showing "The start date Start"?

*/
public boolean isMatch() {
final RecurrenceValidator validator = new RecurrenceValidator(settings);
if (!validator.validateSettings()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this was made a static method we don't need to create a new instance of RecurrenceValidator, this should be called in a static way.

@@ -0,0 +1,105 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was that validation should be done in the model class, moved to the model package (you already did), so did this too have a toString method (You might have cleaned this up.)


- Parameters

| Property | Relevance | Description |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrm9084 I merged all parameters to one table. It's clear that we support these 2 types for recurrence patter. But the shortcoming is that I need to declare the supported and unsupported types for each property.
Please let me know if you have any ideas about how to make it better.


if (!StringUtils.hasText(start) && !StringUtils.hasText(end)) {
if (settings.getStart() == null && settings.getEnd() == null) {
LOGGER.warn("The {} feature filter is not valid for feature {}. It must specify either {}, {}, or both.",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrm9084 Shall we throw exception here?
In the validations of recurrence, I just throw exception, because if there're invalid value for the enum type, it's easier to throw exception. Maybe we need to keep consistent?


| Property | Relevance | Description |
|-----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **type** | Required | Two valid values: `Daily`, `Weekly`. |
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this type can be optional, because we have a default value as Daily.
My concern is that if I announce all parameters to be optional, then maybe users will not define any parameters in recurrence pattern like the following.

start: "Mon, 13 May 2024 02:00:00 GMT",
end: "Mon, 13 May 2024 03:00:00 GMT",
  recurrence:
        range:
          type: "NoEnd"

Shall we support this? Or throw invalid exception since there's no pattern parameter? @mrm9084

/**
* @param numberOfOccurrences the repeat times to be set
* */
public void setNumberOfOccurrences(int numberOfOccurrences) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is different from the setInterval(interger interval)

/**
* @param interval the time units to be set
* */
public void setInterval(Integer interval) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You use int type in public void setNumberOfOccurrences(int numberOfOccurrences)

Is there any reason use Integer here? It doesn't make sense to do null check here, since Interval has default value 1.


// Check whether "Start" is a valid first occurrence
final RecurrencePattern pattern = settings.getRecurrence().getPattern();
if (pattern.getDaysOfWeek().stream().noneMatch((dayOfWeekStr) ->
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be dayOfWeek instead of dayOfWeekStr? getDaysOfWeek returns List

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
azure-spring All azure-spring related issues
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants