feat(alerts): enable tab selection for dashboard alerts/reports (#29096)

This commit is contained in:
Jack
2024-07-30 17:25:19 -05:00
committed by GitHub
parent 6bf8596d68
commit d21d7591c0
11 changed files with 284 additions and 56 deletions

View File

@@ -18,12 +18,14 @@ from datetime import datetime
from unittest.mock import MagicMock, patch
from uuid import uuid4
import pytest
from flask import current_app
from superset.commands.dashboard.permalink.create import CreateDashboardPermalinkCommand
from superset.commands.report.execute import AsyncExecuteReportScheduleCommand
from superset.models.dashboard import Dashboard
from superset.reports.models import ReportSourceFormat
from superset.utils.urls import get_url_path
from tests.integration_tests.fixtures.tabbed_dashboard import (
tabbed_dashboard, # noqa: F401
)
@@ -34,22 +36,21 @@ from tests.integration_tests.reports.utils import create_dashboard_report
@patch(
"superset.commands.report.execute.DashboardScreenshot",
)
@patch(
"superset.commands.dashboard.permalink.create.CreateDashboardPermalinkCommand.run"
@patch.dict(
"superset.extensions.feature_flag_manager._feature_flags", ALERT_REPORT_TABS=True
)
@pytest.mark.usefixtures("login_as_admin")
def test_report_for_dashboard_with_tabs(
create_dashboard_permalink_mock: MagicMock,
dashboard_screenshot_mock: MagicMock,
send_email_smtp_mock: MagicMock,
tabbed_dashboard: Dashboard, # noqa: F811
) -> None:
create_dashboard_permalink_mock.return_value = "permalink"
dashboard_screenshot_mock.get_screenshot.return_value = b"test-image"
current_app.config["ALERT_REPORTS_NOTIFICATION_DRY_RUN"] = False
with create_dashboard_report(
dashboard=tabbed_dashboard,
extra={"active_tabs": ["TAB-L1B", "TAB-L2BB"]},
extra={"dashboard": {"active_tabs": ["TAB-L1B", "TAB-L2BB"]}},
name="test report tabbed dashboard",
) as report_schedule:
dashboard: Dashboard = report_schedule.dashboard
@@ -61,9 +62,12 @@ def test_report_for_dashboard_with_tabs(
str(dashboard.id), dashboard_state
).run()
expected_url = get_url_path("Superset.dashboard_permalink", key=permalink_key)
assert dashboard_screenshot_mock.call_count == 1
url = dashboard_screenshot_mock.call_args.args[0]
assert url.endswith(f"/superset/dashboard/p/{permalink_key}/")
called_url = dashboard_screenshot_mock.call_args.args[0]
assert called_url == expected_url
assert send_email_smtp_mock.call_count == 1
assert len(send_email_smtp_mock.call_args.kwargs["images"]) == 1
@@ -72,22 +76,21 @@ def test_report_for_dashboard_with_tabs(
@patch(
"superset.commands.report.execute.DashboardScreenshot",
)
@patch(
"superset.commands.dashboard.permalink.create.CreateDashboardPermalinkCommand.run"
@patch.dict(
"superset.extensions.feature_flag_manager._feature_flags", ALERT_REPORT_TABS=True
)
@pytest.mark.usefixtures("login_as_admin")
def test_report_with_header_data(
create_dashboard_permalink_mock: MagicMock,
dashboard_screenshot_mock: MagicMock,
send_email_smtp_mock: MagicMock,
tabbed_dashboard: Dashboard, # noqa: F811
) -> None:
create_dashboard_permalink_mock.return_value = "permalink"
dashboard_screenshot_mock.get_screenshot.return_value = b"test-image"
current_app.config["ALERT_REPORTS_NOTIFICATION_DRY_RUN"] = False
with create_dashboard_report(
dashboard=tabbed_dashboard,
extra={"active_tabs": ["TAB-L1B"]},
extra={"dashboard": {"active_tabs": ["TAB-L1B", "TAB-L2BB"]}},
name="test report tabbed dashboard",
) as report_schedule:
dashboard: Dashboard = report_schedule.dashboard
@@ -101,6 +104,7 @@ def test_report_with_header_data(
assert dashboard_screenshot_mock.call_count == 1
url = dashboard_screenshot_mock.call_args.args[0]
assert url.endswith(f"/superset/dashboard/p/{permalink_key}/")
assert send_email_smtp_mock.call_count == 1
header_data = send_email_smtp_mock.call_args.kwargs["header_data"]

View File

@@ -60,6 +60,7 @@ from superset.commands.report.execute import (
)
from superset.commands.report.log_prune import AsyncPruneReportScheduleLogCommand
from superset.exceptions import SupersetException
from superset.key_value.models import KeyValueEntry
from superset.models.core import Database
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
@@ -82,6 +83,9 @@ from tests.integration_tests.fixtures.birth_names_dashboard import (
load_birth_names_dashboard_with_slices, # noqa: F401
load_birth_names_data, # noqa: F401
)
from tests.integration_tests.fixtures.tabbed_dashboard import (
tabbed_dashboard, # noqa: F401
)
from tests.integration_tests.fixtures.world_bank_dashboard import (
load_world_bank_dashboard_with_slices_module_scope, # noqa: F401
load_world_bank_data, # noqa: F401
@@ -91,6 +95,7 @@ from tests.integration_tests.reports.utils import (
create_report_notification,
CSV_FILE,
DEFAULT_OWNER_EMAIL,
reset_key_values,
SCREENSHOT_FILE,
TEST_ID,
)
@@ -1170,6 +1175,93 @@ def test_email_dashboard_report_schedule(
statsd_mock.assert_called_once_with("reports.email.send.ok", 1)
@pytest.mark.usefixtures("tabbed_dashboard")
@patch("superset.utils.screenshots.DashboardScreenshot.get_screenshot")
@patch("superset.reports.notifications.email.send_email_smtp")
@patch.dict(
"superset.extensions.feature_flag_manager._feature_flags", ALERT_REPORT_TABS=True
)
def test_email_dashboard_report_schedule_with_tab_anchor(
_email_mock,
_screenshot_mock,
):
"""
ExecuteReport Command: Test dashboard email report schedule with tab metadata
"""
with freeze_time("2020-01-01T00:00:00Z"):
with patch.object(current_app.config["STATS_LOGGER"], "gauge") as statsd_mock:
# get tabbed dashboard fixture
dashboard = db.session.query(Dashboard).all()[1]
# build report_schedule
report_schedule = create_report_notification(
email_target="target@email.com",
dashboard=dashboard,
extra={"dashboard": {"anchor": "TAB-L2AB"}},
)
AsyncExecuteReportScheduleCommand(
TEST_ID, report_schedule.id, datetime.utcnow()
).run()
# Assert logs are correct
assert_log(ReportState.SUCCESS)
statsd_mock.assert_called_once_with("reports.email.send.ok", 1)
pl = (
db.session.query(KeyValueEntry)
.order_by(KeyValueEntry.id.desc())
.first()
)
value = json.loads(pl.value)
# test that report schedule extra json matches permalink state
assert report_schedule.extra["dashboard"] == value["state"]
# remove report_schedule
cleanup_report_schedule(report_schedule)
# remove permalink kvalues
reset_key_values()
@pytest.mark.usefixtures("tabbed_dashboard")
@patch("superset.utils.screenshots.DashboardScreenshot.get_screenshot")
@patch("superset.reports.notifications.email.send_email_smtp")
@patch.dict(
"superset.extensions.feature_flag_manager._feature_flags", ALERT_REPORT_TABS=False
)
def test_email_dashboard_report_schedule_disabled_tabs(
_email_mock,
_screenshot_mock,
):
"""
ExecuteReport Command: Test dashboard email report schedule with tab metadata
"""
with freeze_time("2020-01-01T00:00:00Z"):
with patch.object(current_app.config["STATS_LOGGER"], "gauge") as statsd_mock:
# get tabbed dashboard fixture
dashboard = db.session.query(Dashboard).all()[1]
# build report_schedule
report_schedule = create_report_notification(
email_target="target@email.com",
dashboard=dashboard,
extra={"dashboard": {"anchor": "TAB-L2AB"}},
)
AsyncExecuteReportScheduleCommand(
TEST_ID, report_schedule.id, datetime.utcnow()
).run()
# Assert logs are correct
assert_log(ReportState.SUCCESS)
statsd_mock.assert_called_once_with("reports.email.send.ok", 1)
permalinks = db.session.query(KeyValueEntry).all()
# test that report schedule extra json matches permalink state
assert len(permalinks) == 0
# remove report_schedule
cleanup_report_schedule(report_schedule)
@pytest.mark.usefixtures(
"load_birth_names_dashboard_with_slices",
"create_report_email_dashboard_force_screenshot",

View File

@@ -22,6 +22,7 @@ from uuid import uuid4
from flask_appbuilder.security.sqla.models import User
from superset import db, security_manager
from superset.key_value.models import KeyValueEntry
from superset.models.core import Database
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
@@ -203,3 +204,8 @@ def create_dashboard_report(dashboard, extra, **kwargs):
if error:
raise error
def reset_key_values() -> None:
db.session.query(KeyValueEntry).delete()
db.session.commit()