feat(alert/reports): adding logic to handle downstream reports when tab is deleted from dashboard (#29333)

This commit is contained in:
Jack
2024-08-23 15:26:26 -05:00
committed by GitHub
parent 9249facaf5
commit 2fda221b7c
5 changed files with 318 additions and 12 deletions

View File

@@ -15,13 +15,14 @@
# specific language governing permissions and limitations
# under the License.
import logging
import textwrap
from functools import partial
from typing import Any, Optional
from flask_appbuilder.models.sqla import Model
from marshmallow import ValidationError
from superset import security_manager
from superset import app, security_manager
from superset.commands.base import BaseCommand, UpdateMixin
from superset.commands.dashboard.exceptions import (
DashboardForbiddenError,
@@ -32,10 +33,13 @@ from superset.commands.dashboard.exceptions import (
)
from superset.commands.utils import populate_roles, update_tags, validate_tags
from superset.daos.dashboard import DashboardDAO
from superset.daos.report import ReportScheduleDAO
from superset.exceptions import SupersetSecurityException
from superset.models.dashboard import Dashboard
from superset.reports.models import ReportSchedule
from superset.tags.models import ObjectType
from superset.utils import json
from superset.utils.core import send_email_smtp
from superset.utils.decorators import on_error, transaction
logger = logging.getLogger(__name__)
@@ -50,7 +54,8 @@ class UpdateDashboardCommand(UpdateMixin, BaseCommand):
@transaction(on_error=partial(on_error, reraise=DashboardUpdateFailedError))
def run(self) -> Model:
self.validate()
assert self._model
assert self._model is not None
self.process_tab_diff()
# Update tags
if (tags := self._properties.pop("tags", None)) is not None:
@@ -112,3 +117,73 @@ class UpdateDashboardCommand(UpdateMixin, BaseCommand):
exceptions.append(ex)
if exceptions:
raise DashboardInvalidError(exceptions=exceptions)
def process_tab_diff(self) -> None:
def find_deleted_tabs() -> list[str]:
position_json = self._properties.get("position_json", "")
current_tabs = self._model.tabs # type: ignore
if position_json and current_tabs:
position = json.loads(position_json)
deleted_tabs = [
tab for tab in current_tabs["all_tabs"] if tab not in position
]
return deleted_tabs
return []
def find_reports_containing_tabs(tabs: list[str]) -> list[ReportSchedule]:
alert_reports_list = []
for tab in tabs:
for report in ReportScheduleDAO.find_by_extra_metadata(tab):
alert_reports_list.append(report)
return alert_reports_list
def send_deactivated_email_warning(report: ReportSchedule) -> None:
description = textwrap.dedent(
"""
The dashboard tab used in this report has been deleted and your report has been deactivated.
Please update your report settings to remove or change the tab used.
"""
)
html_content = textwrap.dedent(
f"""
<html>
<head>
<style type="text/css">
table, th, td {{
border-collapse: collapse;
border-color: rgb(200, 212, 227);
color: rgb(42, 63, 95);
padding: 4px 8px;
}}
.image{{
margin-bottom: 18px;
}}
</style>
</head>
<body>
<div>{description}</div>
<br>
</body>
</html>
"""
)
for report_owner in report.owners:
if email := report_owner.email:
send_email_smtp(
to=email,
subject=f"[Report: {report.name}] Deactivated",
html_content=html_content,
config=app.config,
)
def deactivate_reports(reports_list: list[ReportSchedule]) -> None:
for report in reports_list:
# deactivate
ReportScheduleDAO.update(report, {"active": False})
# send email to report owner
send_deactivated_email_warning(report)
deleted_tabs = find_deleted_tabs()
reports = find_reports_containing_tabs(deleted_tabs)
deactivate_reports(reports)