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 64 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


- 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?

Copy link
Member

Choose a reason for hiding this comment

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

No, I would double check with DotNet, but I think we decided most "invalid" cases result in logs and disabled features.

A similar thing happens with requesting a feature flag that doesn't exist, you just get false returned.

Copy link
Member

Choose a reason for hiding this comment

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

It seems that we may have had a design change where we now throw more errors than just fail. We need to be careful that we don't make a previous valid setup/non error setup to throw an error as that would be a breaking change.


| 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

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
numberOfOccurrences: 3
```

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). To create a recurrence rule, you must specify both `pattern` and `range`. Any pattern type can work with any range type.
Copy link
Member

Choose a reason for hiding this comment

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

It should be 3/4 parts as the start/end are required and give the period of time that the flag is enabled for.

Copy link
Member

Choose a reason for hiding this comment

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

Also, we should mention start/end here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I use "2 parts" because there's only 2 parameters behind the "recurrence" parameter.
I agree that we should mention start/end here.

Updated to the following description.

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. 


#### Recurrence Pattern

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 "every other Friday".
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this does a good job explaining Daily/Weekly. It also doesn't help that a single period may not be over 24 hours but can take place over 2 days.

Daily is the Days that have passed, which isn't actually Daily. Same with Weekly. Either that or I misunderstand how this works, which is also an issue.

You should separate the Daily and Weekly examples from each other so you know which one a person may want.

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 the following

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".

sdk/spring/spring-cloud-azure-feature-management/README.md Outdated Show resolved Hide resolved
interval: 2,
daysOfWeek:
- Monday
- Tuesday
Copy link
Member

Choose a reason for hiding this comment

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

Have you tried using this yaml. I get a json parsing error when trying to use it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, updated

/**
* A recurrence definition describing how time window recurs
* */
public class Recurrence {
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 add the Annotation to all of the models to ignore unknown fields. It will save us at some point if a field is added to the portal by default. Wouldn't be the first 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.

Updated

/**
* The date to stop applying the recurrence pattern
* */
private ZonedDateTime endDate = Instant.ofEpochMilli(Integer.MAX_VALUE).atZone(ZoneOffset.UTC);
Copy link
Member

Choose a reason for hiding this comment

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

What is the intent of this? Is it just an unrealistic end date to cause errors? Also, why not just 0 instead of max int? The number of milliseconds of a max int is just a few days. So instead of 01-01-1970 it's 01-25-1970?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed this default value.

Property | Relevance | Description |
-----------|---------------|-------------
**type** | Required | Three valid values: `NoEnd`, `EndDate` and `Numbered`.
**endDate** | 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, not applicable for `NoEnd` and `Numbered` type.
Copy link
Member

Choose a reason for hiding this comment

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

endDate currently has a default value. It's not "required", The end date just has to be before 1970-01-25T20:31:23.647Z

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed the default value of endDate. It sounds not reasonable that we have some default value.

/**
* The number of times to repeat the time window
*/
private int numberOfOccurrences = Integer.MAX_VALUE;
Copy link
Member

Choose a reason for hiding this comment

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

This shouldn't have a default value if it's a required field. Right now the default value makes it effectively a no end filter.

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. I agree that no need to set a default value.


**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 Range
Copy link
Member

Choose a reason for hiding this comment

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

Have you tested this? I have no exception is thrown for me when I make one with it.


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
Member

Choose a reason for hiding this comment

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

It seems that we may have had a design change where we now throw more errors than just fail. We need to be careful that we don't make a previous valid setup/non error setup to throw an error as that would be a breaking change.

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