Skip to content

Commit

Permalink
feat(report): capture dashboard reports in specific states
Browse files Browse the repository at this point in the history
  • Loading branch information
ktmud committed Jul 15, 2022
1 parent 2cb4fd3 commit b04fd2e
Show file tree
Hide file tree
Showing 38 changed files with 561 additions and 360 deletions.
6 changes: 4 additions & 2 deletions superset/dashboards/permalink/commands/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,10 @@

class CreateDashboardPermalinkCommand(BaseDashboardPermalinkCommand):
"""
Get or create a permalink key for the given dashboard in certain state.
Will reuse the key for the same user and dashboard state.
Get or create a permalink key for the dashboard.
The same dashboard_id and state for the same user will return the
same permalink.
"""

def __init__(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from sqlalchemy.ext.declarative import declarative_base

from superset import db
from superset.models.reports import ReportState
from superset.reports.models import ReportState

Base = declarative_base()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
"""rename_report_schedule_extra_to_extra_json
So we can reuse the ExtraJSONMixin
Revision ID: ffa79af61a56
Revises: 06e1e70058c7
Create Date: 2022-07-11 11:26:00.010714
"""

# revision identifiers, used by Alembic.
revision = "ffa79af61a56"
down_revision = "06e1e70058c7"

from alembic import op
from sqlalchemy.types import Text


def upgrade():
op.alter_column(
"report_schedule",
"extra",
new_column_name="extra_json",
# existing info is required for MySQL
existing_type=Text,
existing_nullable=False,
)


def downgrade():
op.alter_column(
"report_schedule",
"extra_json",
new_column_name="extra",
existing_type=Text,
existing_nullable=False,
)
6 changes: 4 additions & 2 deletions superset/models/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,13 +438,15 @@ def export_dashboards( # pylint: disable=too-many-locals
)

@classmethod
def get(cls, id_or_slug: str) -> Dashboard:
def get(cls, id_or_slug: Union[str, int]) -> Dashboard:
session = db.session()
qry = session.query(Dashboard).filter(id_or_slug_filter(id_or_slug))
return qry.one_or_none()


def id_or_slug_filter(id_or_slug: str) -> BinaryExpression:
def id_or_slug_filter(id_or_slug: Union[int, str]) -> BinaryExpression:
if isinstance(id_or_slug, int):
return Dashboard.id == id_or_slug
if id_or_slug.isdigit():
return Dashboard.id == int(id_or_slug)
return Dashboard.slug == id_or_slug
Expand Down
3 changes: 2 additions & 1 deletion superset/models/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,8 @@ def extra(self) -> Dict[str, Any]:
)
return {}

def set_extra_json(self, extras: Dict[str, Any]) -> None:
@extra.setter
def extra(self, extras: Dict[str, Any]) -> None:
self.extra_json = json.dumps(extras)

def set_extra_json_key(self, key: str, value: Any) -> None:
Expand Down
5 changes: 4 additions & 1 deletion superset/reports/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
from superset.dashboards.filters import DashboardAccessFilter
from superset.databases.filters import DatabaseFilter
from superset.extensions import event_logger
from superset.models.reports import ReportSchedule
from superset.reports.commands.bulk_delete import BulkDeleteReportScheduleCommand
from superset.reports.commands.create import CreateReportScheduleCommand
from superset.reports.commands.delete import DeleteReportScheduleCommand
Expand All @@ -45,6 +44,7 @@
)
from superset.reports.commands.update import UpdateReportScheduleCommand
from superset.reports.filters import ReportScheduleAllTextFilter
from superset.reports.models import ReportSchedule
from superset.reports.schemas import (
get_delete_ids_schema,
openapi_spec_methods_override,
Expand Down Expand Up @@ -94,6 +94,7 @@ def ensure_alert_reports_enabled(self) -> Optional[Response]:
"database.database_name",
"database.id",
"description",
"extra",
"force_screenshot",
"grace_period",
"last_eval_dttm",
Expand Down Expand Up @@ -135,6 +136,7 @@ def ensure_alert_reports_enabled(self) -> Optional[Response]:
"crontab_humanized",
"dashboard_id",
"description",
"extra",
"id",
"last_eval_dttm",
"last_state",
Expand All @@ -156,6 +158,7 @@ def ensure_alert_reports_enabled(self) -> Optional[Response]:
"dashboard",
"database",
"description",
"extra",
"force_screenshot",
"grace_period",
"log_retention",
Expand Down
2 changes: 1 addition & 1 deletion superset/reports/commands/alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@

from superset import app, jinja_context, security_manager
from superset.commands.base import BaseCommand
from superset.models.reports import ReportSchedule, ReportScheduleValidatorType
from superset.reports.commands.exceptions import (
AlertQueryError,
AlertQueryInvalidTypeError,
Expand All @@ -36,6 +35,7 @@
AlertQueryTimeout,
AlertValidatorConfigError,
)
from superset.reports.models import ReportSchedule, ReportScheduleValidatorType
from superset.utils.core import override_user
from superset.utils.retries import retry_call

Expand Down
3 changes: 2 additions & 1 deletion superset/reports/commands/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@
from superset.charts.dao import ChartDAO
from superset.commands.base import BaseCommand
from superset.dashboards.dao import DashboardDAO
from superset.models.reports import ReportCreationMethod
from superset.reports.commands.exceptions import (
ChartNotFoundValidationError,
ChartNotSavedValidationError,
DashboardNotFoundValidationError,
DashboardNotSavedValidationError,
ReportScheduleChartOrDashboardValidationError,
)
from superset.reports.models import ReportCreationMethod

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -63,6 +63,7 @@ def validate_chart_dashboard(

if chart_id and dashboard_id:
exceptions.append(ReportScheduleChartOrDashboardValidationError())

if chart_id:
chart = ChartDAO.find_by_id(chart_id)
if not chart:
Expand Down
2 changes: 1 addition & 1 deletion superset/reports/commands/bulk_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
from superset.commands.base import BaseCommand
from superset.dao.exceptions import DAODeleteFailedError
from superset.exceptions import SupersetSecurityException
from superset.models.reports import ReportSchedule
from superset.reports.commands.exceptions import (
ReportScheduleBulkDeleteFailedError,
ReportScheduleForbiddenError,
ReportScheduleNotFoundError,
)
from superset.reports.dao import ReportScheduleDAO
from superset.reports.models import ReportSchedule

logger = logging.getLogger(__name__)

Expand Down
35 changes: 24 additions & 11 deletions superset/reports/commands/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,12 @@
import logging
from typing import Any, Dict, List, Optional

from flask_appbuilder.models.sqla import Model
from flask_babel import gettext as _
from marshmallow import ValidationError

from superset.commands.base import CreateMixin
from superset.dao.exceptions import DAOCreateFailedError
from superset.databases.dao import DatabaseDAO
from superset.models.reports import ReportCreationMethod, ReportScheduleType
from superset.reports.commands.base import BaseReportScheduleCommand
from superset.reports.commands.exceptions import (
DatabaseNotFoundValidationError,
Expand All @@ -36,6 +35,12 @@
ReportScheduleRequiredTypeValidationError,
)
from superset.reports.dao import ReportScheduleDAO
from superset.reports.models import (
ReportCreationMethod,
ReportSchedule,
ReportScheduleType,
)
from superset.reports.types import ReportScheduleExtra

logger = logging.getLogger(__name__)

Expand All @@ -44,7 +49,7 @@ class CreateReportScheduleCommand(CreateMixin, BaseReportScheduleCommand):
def __init__(self, data: Dict[str, Any]):
self._properties = data.copy()

def run(self) -> Model:
def run(self) -> ReportSchedule:
self.validate()
try:
report_schedule = ReportScheduleDAO.create(self._properties)
Expand Down Expand Up @@ -117,20 +122,28 @@ def validate(self) -> None:
raise exception

def _validate_report_extra(self, exceptions: List[ValidationError]) -> None:
extra = self._properties.get("extra")
extra: Optional[ReportScheduleExtra] = self._properties.get("extra")
dashboard = self._properties.get("dashboard")

if extra is None or dashboard is None:
return

dashboard_tab_ids = extra.get("dashboard_tab_ids")
if dashboard_tab_ids is None:
dashboard_state = extra.get("dashboard")
if not dashboard_state:
return
position_data = json.loads(dashboard.position_json)
invalid_tab_ids = [
tab_id for tab_id in dashboard_tab_ids if tab_id not in position_data
]

position_data = json.loads(dashboard.position_json or "{}")
active_tabs = dashboard_state.get("activeTabs") or []
anchor = dashboard_state.get("anchor")
invalid_tab_ids = {
tab_id for tab_id in active_tabs if tab_id not in position_data
}
if anchor and anchor not in position_data:
invalid_tab_ids.add(anchor)
if invalid_tab_ids:
exceptions.append(
ValidationError(f"Invalid tab IDs selected: {invalid_tab_ids}", "extra")
ValidationError(
_("Invalid tab ids: %s(tab_ids)", tab_ids=str(invalid_tab_ids)),
"extra",
)
)
2 changes: 1 addition & 1 deletion superset/reports/commands/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@
from superset.commands.base import BaseCommand
from superset.dao.exceptions import DAODeleteFailedError
from superset.exceptions import SupersetSecurityException
from superset.models.reports import ReportSchedule
from superset.reports.commands.exceptions import (
ReportScheduleDeleteFailedError,
ReportScheduleForbiddenError,
ReportScheduleNotFoundError,
)
from superset.reports.dao import ReportScheduleDAO
from superset.reports.models import ReportSchedule

logger = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion superset/reports/commands/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
ForbiddenError,
ValidationError,
)
from superset.models.reports import ReportScheduleType
from superset.reports.models import ReportScheduleType


class DatabaseNotFoundValidationError(ValidationError):
Expand Down

0 comments on commit b04fd2e

Please sign in to comment.