From 62fb9740d3cf93d5b2fec9fb3b1ce351404a58bc Mon Sep 17 00:00:00 2001 From: Arash Date: Fri, 20 Aug 2021 12:18:31 -0400 Subject: [PATCH] backend creation method validation --- superset/models/dashboard.py | 4 +--- superset/models/slice.py | 2 +- superset/reports/commands/create.py | 17 ++++++++++++++++- superset/reports/commands/exceptions.py | 12 ++++++++++++ superset/reports/dao.py | 18 ++++++++++++++++++ 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py index d74efa72cde8f..8a9abc8ce2777 100644 --- a/superset/models/dashboard.py +++ b/superset/models/dashboard.py @@ -131,9 +131,7 @@ def copy_dashboard( ) -class Dashboard( # pylint: disable=too-many-instance-attributes - Model, AuditMixinNullable, ImportExportMixin -): +class Dashboard(Model, AuditMixinNullable, ImportExportMixin): """The dashboard object!""" diff --git a/superset/models/slice.py b/superset/models/slice.py index 310343a1f0a26..40573396cfa45 100644 --- a/superset/models/slice.py +++ b/superset/models/slice.py @@ -53,7 +53,7 @@ logger = logging.getLogger(__name__) -class Slice( # pylint: disable=too-many-instance-attributes,too-many-public-methods +class Slice( # pylint: disable=too-many-instance-attributes, too-many-public-methods Model, AuditMixinNullable, ImportExportMixin ): """A slice is essentially a report or a view on data""" diff --git a/superset/reports/commands/create.py b/superset/reports/commands/create.py index 23ed27e89c440..f14773bea2fea 100644 --- a/superset/reports/commands/create.py +++ b/superset/reports/commands/create.py @@ -25,12 +25,13 @@ from superset.commands.base import CreateMixin from superset.dao.exceptions import DAOCreateFailedError from superset.databases.dao import DatabaseDAO -from superset.models.reports import ReportScheduleType +from superset.models.reports import ReportCreationMethodType, ReportScheduleType from superset.reports.commands.base import BaseReportScheduleCommand from superset.reports.commands.exceptions import ( DatabaseNotFoundValidationError, ReportScheduleAlertRequiredDatabaseValidationError, ReportScheduleCreateFailedError, + ReportScheduleCreationMethodUniquenessValidationError, ReportScheduleInvalidError, ReportScheduleNameUniquenessValidationError, ReportScheduleRequiredTypeValidationError, @@ -59,6 +60,10 @@ def validate(self) -> None: owner_ids: Optional[List[int]] = self._properties.get("owners") name = self._properties.get("name", "") report_type = self._properties.get("type") + creation_method = self._properties.get("creation_method") + chart_id = self._properties.get("chart_id") + dashboard_id = self._properties.get("dashboard_id") + user_id = self._actor.id # Validate type is required if not report_type: @@ -84,6 +89,16 @@ def validate(self) -> None: # Validate chart or dashboard relations self.validate_chart_dashboard(exceptions) + # Validate that each chart or dashboard only has one report with + # the respective creation method. + if ( + creation_method != ReportCreationMethodType.ALERTS_REPORTS + and not ReportScheduleDAO.validate_unique_creation_method( + user_id, dashboard_id, chart_id + ) + ): + exceptions.append(ReportScheduleCreationMethodUniquenessValidationError) + if "validator_config_json" in self._properties: self._properties["validator_config_json"] = json.dumps( self._properties["validator_config_json"] diff --git a/superset/reports/commands/exceptions.py b/superset/reports/commands/exceptions.py index dfe2402da0449..ce53436afba8b 100644 --- a/superset/reports/commands/exceptions.py +++ b/superset/reports/commands/exceptions.py @@ -137,6 +137,18 @@ def __init__(self) -> None: super().__init__([_("Name must be unique")], field_name="name") +class ReportScheduleCreationMethodUniquenessValidationError(ValidationError): + """ + Marshmallow validation error for Report Schedule with creation method charts + or dashboards already existing on a report. + """ + + def __init__(self) -> None: + super().__init__( + [_("Resource already has an attached report")], field_name="creation_method" + ) + + class AlertQueryMultipleRowsError(CommandException): message = _("Alert query returned more then one row.") diff --git a/superset/reports/dao.py b/superset/reports/dao.py index ce69ba004c470..6b68da3dafba9 100644 --- a/superset/reports/dao.py +++ b/superset/reports/dao.py @@ -113,6 +113,24 @@ def bulk_delete( db.session.rollback() raise DAODeleteFailedError(str(ex)) from ex + @staticmethod + def validate_unique_creation_method( + user_id: int, dashboard_id: Optional[int] = None, chart_id: Optional[int] = None + ) -> bool: + """ + Validate if the user already has a chart or dashboard + with a report attached form the self subscribe reports + """ + query = db.session.query(ReportSchedule).filter( + ReportSchedule.created_by_fk == user_id + ) + if dashboard_id is not None: + query = query.filter(ReportSchedule.dashboard_id == dashboard_id) + + if chart_id is not None: + query = query.filter(ReportSchedule.chart_id == chart_id) + return query.one_or_none() + @staticmethod def validate_update_uniqueness( name: str, report_type: str, report_schedule_id: Optional[int] = None