From 6478bb7eabad4bc352577b900a0834327b67f817 Mon Sep 17 00:00:00 2001 From: Steven Liu Date: Wed, 29 Jan 2025 04:33:41 +1100 Subject: [PATCH] feat: add date format to the email subject (#31413) Co-authored-by: Steven Liu --- RESOURCES/FEATURE_FLAGS.md | 1 + UPDATING.md | 1 + docs/docs/configuration/alerts-reports.mdx | 3 ++ superset/config.py | 2 + superset/reports/notifications/email.py | 27 ++++++++++-- .../reports/notifications/email_tests.py | 42 +++++++++++++++++++ 6 files changed, 72 insertions(+), 4 deletions(-) diff --git a/RESOURCES/FEATURE_FLAGS.md b/RESOURCES/FEATURE_FLAGS.md index 3855729f3..39f1fe199 100644 --- a/RESOURCES/FEATURE_FLAGS.md +++ b/RESOURCES/FEATURE_FLAGS.md @@ -45,6 +45,7 @@ These features are **finished** but currently being tested. They are usable, but - CACHE_IMPERSONATION - CONFIRM_DASHBOARD_DIFF - DYNAMIC_PLUGINS +- DATE_FORMAT_IN_EMAIL_SUBJECT: [(docs)](https://superset.apache.org/docs/configuration/alerts-reports#commons) - ENABLE_SUPERSET_META_DB: [(docs)](https://superset.apache.org/docs/configuration/databases/#querying-across-databases) - ESTIMATE_QUERY_COST - GLOBAL_ASYNC_QUERIES [(docs)](https://github.com/apache/superset/blob/master/CONTRIBUTING.md#async-chart-queries) diff --git a/UPDATING.md b/UPDATING.md index 65161ac1e..9216650ba 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -37,6 +37,7 @@ assists people when migrating to a new version. - [30099](https://github.com/apache/superset/pull/30099) Translations are no longer included in the default docker image builds. If your environment requires translations, you'll want to set the docker build arg `BUILD_TRANSACTION=true`. - [31262](https://github.com/apache/superset/pull/31262) NOTE: deprecated `pylint` in favor of `ruff` as our only python linter. Only affect development workflows positively (not the release itself). It should cover most important rules, be much faster, but some things linting rules that were enforced before may not be enforce in the exact same way as before. - [31173](https://github.com/apache/superset/pull/31173) Modified `fetch_csrf_token` to align with HTTP standards, particularly regarding how cookies are handled. If you encounter any issues related to CSRF functionality, please report them as a new issue and reference this PR for context. +- [31413](https://github.com/apache/superset/pull/31413) Enable the DATE_FORMAT_IN_EMAIL_SUBJECT feature flag to allow users to specify a date format for the email subject, which will then be replaced with the actual date. - [31385](https://github.com/apache/superset/pull/31385) Significant docker refactor, reducing access levels for the `superset` user, streamlining layer building, ... - [31503](https://github.com/apache/superset/pull/31503) Deprecating python 3.9.x support, 3.11 is now the recommended version and 3.10 is still supported over the Superset 5.0 lifecycle. - [29121](https://github.com/apache/superset/pull/29121) Removed the `css`, `position_json`, and `json_metadata` from the payload of the dashboard list endpoint (`GET api/v1/dashboard`) for performance reasons. diff --git a/docs/docs/configuration/alerts-reports.mdx b/docs/docs/configuration/alerts-reports.mdx index 5ff1ef4b8..361d6cfbe 100644 --- a/docs/docs/configuration/alerts-reports.mdx +++ b/docs/docs/configuration/alerts-reports.mdx @@ -25,6 +25,9 @@ Alerts and reports are disabled by default. To turn them on, you need to do some - At least one of those must be configured, depending on what you want to use: - emails: `SMTP_*` settings - Slack messages: `SLACK_API_TOKEN` +- Users can customize the email subject by including date code placeholders, which will automatically be replaced with the corresponding UTC date when the email is sent. To enable this functionality, activate the `"DATE_FORMAT_IN_EMAIL_SUBJECT"` [feature flag](/docs/configuration/configuring-superset#feature-flags). This enables date formatting in email subjects, preventing all reporting emails from being grouped into the same thread (optional for the reporting feature). + - Use date codes from [strftime.org](https://strftime.org/) to create the email subject. + - If no date code is provided, the original string will be used as the email subject. ##### Disable dry-run mode diff --git a/superset/config.py b/superset/config.py index 7f6b24f68..8554b2a26 100644 --- a/superset/config.py +++ b/superset/config.py @@ -559,6 +559,8 @@ DEFAULT_FEATURE_FLAGS: dict[str, bool] = { # If on, you'll want to add "https://avatars.slack-edge.com" to the list of allowed # domains in your TALISMAN_CONFIG "SLACK_ENABLE_AVATARS": False, + # Allow users to optionally specify date formats in email subjects, which will be parsed if enabled. # noqa: E501 + "DATE_FORMAT_IN_EMAIL_SUBJECT": False, } # ------------------------------ diff --git a/superset/reports/notifications/email.py b/superset/reports/notifications/email.py index 181d4aa6a..e5bf73547 100644 --- a/superset/reports/notifications/email.py +++ b/superset/reports/notifications/email.py @@ -17,13 +17,15 @@ import logging import textwrap from dataclasses import dataclass +from datetime import datetime from email.utils import make_msgid, parseaddr from typing import Any, Optional import nh3 from flask_babel import gettext as __ +from pytz import timezone -from superset import app +from superset import app, is_feature_enabled from superset.exceptions import SupersetErrorsException from superset.reports.models import ReportRecipientType from superset.reports.notifications.base import BaseNotification @@ -79,6 +81,16 @@ class EmailNotification(BaseNotification): # pylint: disable=too-few-public-met """ type = ReportRecipientType.EMAIL + now = datetime.now(timezone("UTC")) + + @property + def _name(self) -> str: + """Include date format in the name if feature flag is enabled""" + return ( + self._parse_name(self._content.name) + if is_feature_enabled("DATE_FORMAT_IN_EMAIL_SUBJECT") + else self._content.name + ) @staticmethod def _get_smtp_domain() -> str: @@ -173,11 +185,11 @@ class EmailNotification(BaseNotification): # pylint: disable=too-few-public-met ) csv_data = None if self._content.csv: - csv_data = {__("%(name)s.csv", name=self._content.name): self._content.csv} + csv_data = {__("%(name)s.csv", name=self._name): self._content.csv} pdf_data = None if self._content.pdf: - pdf_data = {__("%(name)s.pdf", name=self._content.name): self._content.pdf} + pdf_data = {__("%(name)s.pdf", name=self._name): self._content.pdf} return EmailContent( body=body, @@ -191,9 +203,16 @@ class EmailNotification(BaseNotification): # pylint: disable=too-few-public-met return __( "%(prefix)s %(title)s", prefix=app.config["EMAIL_REPORTS_SUBJECT_PREFIX"], - title=self._content.name, + title=self._name, ) + def _parse_name(self, name: str) -> str: + """If user add a date format to the subject, parse it to the real date + This feature is hidden behind a feature flag `DATE_FORMAT_IN_EMAIL_SUBJECT` + by default it is disabled + """ + return self.now.strftime(name) + def _get_call_to_action(self) -> str: return __(app.config["EMAIL_REPORTS_CTA"]) diff --git a/tests/unit_tests/reports/notifications/email_tests.py b/tests/unit_tests/reports/notifications/email_tests.py index ab3fa8c5a..d8872d4d3 100644 --- a/tests/unit_tests/reports/notifications/email_tests.py +++ b/tests/unit_tests/reports/notifications/email_tests.py @@ -14,7 +14,12 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +from datetime import datetime + import pandas as pd +from pytz import timezone + +from tests.unit_tests.conftest import with_feature_flags def test_render_description_with_html() -> None: @@ -56,3 +61,40 @@ def test_render_description_with_html() -> None: in email_body ) assert '<a href="http://www.example.com">333</a>' in email_body + + +@with_feature_flags(DATE_FORMAT_IN_EMAIL_SUBJECT=True) +def test_email_subject_with_datetime() -> None: + # `superset.models.helpers`, a dependency of following imports, + # requires app context + from superset.reports.models import ReportRecipients, ReportRecipientType + from superset.reports.notifications.base import NotificationContent + from superset.reports.notifications.email import EmailNotification + + now = datetime.now(timezone("UTC")) + + datetime_pattern = "%Y-%m-%d" + + content = NotificationContent( + name=f"test alert {datetime_pattern}", + embedded_data=pd.DataFrame( + { + "A": [1, 2, 3], + } + ), + description='

This is a test alert


', + header_data={ + "notification_format": "PNG", + "notification_type": "Alert", + "owners": [1], + "notification_source": None, + "chart_id": None, + "dashboard_id": None, + "slack_channels": None, + }, + ) + subject = EmailNotification( + recipient=ReportRecipients(type=ReportRecipientType.EMAIL), content=content + )._get_subject() + assert datetime_pattern not in subject + assert now.strftime(datetime_pattern) in subject