From 3dadefcfb0f0f2da159bec549ca1ed87263a31b6 Mon Sep 17 00:00:00 2001 From: Vitor Avila <96086495+Vitor-Avila@users.noreply.github.com> Date: Thu, 13 Jun 2024 19:12:45 -0300 Subject: [PATCH] feat: Support a dynamic minimum interval for alerts and reports (#29241) --- docs/docs/configuration/alerts-reports.mdx | 11 +++++ superset/commands/report/base.py | 2 + superset/config.py | 1 + tests/unit_tests/commands/report/base_test.py | 46 ++++++++++++++++++- 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/alerts-reports.mdx b/docs/docs/configuration/alerts-reports.mdx index d489b6ebd..ed4e739ae 100644 --- a/docs/docs/configuration/alerts-reports.mdx +++ b/docs/docs/configuration/alerts-reports.mdx @@ -204,6 +204,17 @@ ALERT_MINIMUM_INTERVAL = int(timedelta(minutes=10).total_seconds()) REPORT_MINIMUM_INTERVAL = int(timedelta(minutes=5).total_seconds()) ``` +Alternatively, you can assign a function to `ALERT_MINIMUM_INTERVAL` and/or `REPORT_MINIMUM_INTERVAL`. This is useful to dynamically retrieve a value as needed: + +``` python +def alert_dynamic_minimal_interval(**kwargs) -> int: + """ + Define logic here to retrieve the value dynamically + """ + +ALERT_MINIMUM_INTERVAL = alert_dynamic_minimal_interval +``` + ## Custom Dockerfile If you're running the dev version of a released Superset image, like `apache/superset:3.1.0-dev`, you should be set with the above. diff --git a/superset/commands/report/base.py b/superset/commands/report/base.py index 3086023f0..199f985d0 100644 --- a/superset/commands/report/base.py +++ b/superset/commands/report/base.py @@ -98,6 +98,8 @@ class BaseReportScheduleCommand(BaseCommand): else "REPORT_MINIMUM_INTERVAL" ) minimum_interval = current_app.config.get(config_key, 0) + if callable(minimum_interval): + minimum_interval = minimum_interval() if not isinstance(minimum_interval, int): logger.error( diff --git a/superset/config.py b/superset/config.py index ea3ac079c..3dfeb3ad4 100644 --- a/superset/config.py +++ b/superset/config.py @@ -1382,6 +1382,7 @@ ALERT_REPORTS_MIN_CUSTOM_SCREENSHOT_WIDTH = 600 ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH = 2400 # Set a minimum interval threshold between executions (for each Alert/Report) # Value should be an integer i.e. int(timedelta(minutes=5).total_seconds()) +# You can also assign a function to the config that returns the expected integer ALERT_MINIMUM_INTERVAL = int(timedelta(minutes=0).total_seconds()) REPORT_MINIMUM_INTERVAL = int(timedelta(minutes=0).total_seconds()) diff --git a/tests/unit_tests/commands/report/base_test.py b/tests/unit_tests/commands/report/base_test.py index 871f3a511..61d233535 100644 --- a/tests/unit_tests/commands/report/base_test.py +++ b/tests/unit_tests/commands/report/base_test.py @@ -52,9 +52,17 @@ TEST_SCHEDULES_SINGLE_MINUTES = { TEST_SCHEDULES = TEST_SCHEDULES_EVERY_MINUTE.union(TEST_SCHEDULES_SINGLE_MINUTES) +def dynamic_alert_minimum_interval(**kwargs) -> int: + return int(timedelta(minutes=10).total_seconds()) + + +def dynamic_report_minimum_interval(**kwargs) -> int: + return int(timedelta(minutes=5).total_seconds()) + + def app_custom_config( - alert_minimum_interval: int | str = 0, - report_minimum_interval: int | str = 0, + alert_minimum_interval: int | str | Callable[[], int] = 0, + report_minimum_interval: int | str | Callable[[], int] = 0, ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: """ Decorator to mock the current_app.config values dynamically for each test. @@ -253,3 +261,37 @@ def test_validate_report_frequency_invalid_config( f"invalid value for {report_type}_MINIMUM_INTERVAL: 10 minutes" ) assert expected_error_message.lower() in caplog.text.lower() + + +@app_custom_config( + alert_minimum_interval=dynamic_alert_minimum_interval, + report_minimum_interval=dynamic_report_minimum_interval, +) +def test_validate_report_frequency_using_callable() -> None: + """ + Test the ``validate_report_frequency`` method when the config + values are set to a function. + """ + # Should fail with a 9 minutes interval, and work with 10 + with pytest.raises(ReportScheduleFrequencyNotAllowed): + BaseReportScheduleCommand().validate_report_frequency( + "1,10 * * * *", + ReportScheduleType.ALERT, + ) + + BaseReportScheduleCommand().validate_report_frequency( + "1,11 * * * *", + ReportScheduleType.ALERT, + ) + + # Should fail with a 4 minutes interval, and work with 5 + with pytest.raises(ReportScheduleFrequencyNotAllowed): + BaseReportScheduleCommand().validate_report_frequency( + "1,5 * * * *", + ReportScheduleType.REPORT, + ) + + BaseReportScheduleCommand().validate_report_frequency( + "1,6 * * * *", + ReportScheduleType.REPORT, + )