diff --git a/superset/models/reports.py b/superset/models/reports.py index 73c74ebc502..f0bbcacd276 100644 --- a/superset/models/reports.py +++ b/superset/models/reports.py @@ -74,6 +74,12 @@ class ReportDataFormat(str, enum.Enum): DATA = "CSV" +class ReportCreationMethodType(str, enum.Enum): + CHARTS = "charts" + DASHBOARDS = "dashboards" + ALERTS_REPORTS = "alerts_reports" + + report_schedule_user = Table( "report_schedule_user", metadata, @@ -102,6 +108,9 @@ class ReportSchedule(Model, AuditMixinNullable): context_markdown = Column(Text) active = Column(Boolean, default=True, index=True) crontab = Column(String(1000), nullable=False) + creation_method = Column( + String(255), server_default=ReportCreationMethodType.ALERTS_REPORTS + ) report_format = Column(String(50), default=ReportDataFormat.VISUALIZATION) sql = Column(Text()) # (Alerts/Reports) M-O to chart diff --git a/superset/reports/api.py b/superset/reports/api.py index c9efae1293b..ccbda74b1f2 100644 --- a/superset/reports/api.py +++ b/superset/reports/api.py @@ -84,6 +84,7 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi): "chart.id", "chart.slice_name", "context_markdown", + "creation_method", "crontab", "dashboard.dashboard_title", "dashboard.id", @@ -123,6 +124,7 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi): "created_by.first_name", "created_by.last_name", "created_on", + "creation_method", "crontab", "crontab_humanized", "id", @@ -140,6 +142,7 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi): "active", "chart", "context_markdown", + "creation_method", "crontab", "dashboard", "database", @@ -173,7 +176,13 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi): "type", "crontab_humanized", ] - search_columns = ["name", "active", "created_by", "type", "last_state"] + search_columns = [ + "name", + "active", + "created_by", + "type", + "last_state", + ] search_filters = {"name": [ReportScheduleAllTextFilter]} allowed_rel_fields = {"owners", "chart", "dashboard", "database", "created_by"} filter_rel_fields = { diff --git a/superset/reports/schemas.py b/superset/reports/schemas.py index 1ea711f4cd0..acbb6583a1b 100644 --- a/superset/reports/schemas.py +++ b/superset/reports/schemas.py @@ -20,8 +20,10 @@ from croniter import croniter from flask_babel import gettext as _ from marshmallow import fields, Schema, validate, validates_schema from marshmallow.validate import Length, Range, ValidationError +from marshmallow_enum import EnumField from superset.models.reports import ( + ReportCreationMethodType, ReportDataFormat, ReportRecipientType, ReportScheduleType, @@ -83,6 +85,10 @@ working_timeout_description = ( "If an alert is staled at a working state, how long until it's state is reseted to" " error" ) +creation_method_description = ( + "Creation method is used to inform the frontend whether the report/alert was " + "created in the dashboard, chart, or alerts and reports UI." +) def validate_crontab(value: Union[bytes, bytearray, str]) -> None: @@ -150,6 +156,12 @@ class ReportSchedulePostSchema(Schema): description=sql_description, example="SELECT value FROM time_series_table" ) chart = fields.Integer(required=False, allow_none=True) + creation_method = EnumField( + ReportCreationMethodType, + by_value=True, + required=True, + description=creation_method_description, + ) dashboard = fields.Integer(required=False, allow_none=True) database = fields.Integer(required=False) owners = fields.List(fields.Integer(description=owners_description)) @@ -226,6 +238,12 @@ class ReportSchedulePutSchema(Schema): allow_none=True, ) chart = fields.Integer(required=False, allow_none=True) + creation_method = EnumField( + ReportCreationMethodType, + by_value=True, + allow_none=True, + description=creation_method_description, + ) dashboard = fields.Integer(required=False, allow_none=True) database = fields.Integer(required=False) owners = fields.List(fields.Integer(description=owners_description), required=False) diff --git a/tests/integration_tests/reports/api_tests.py b/tests/integration_tests/reports/api_tests.py index 995aa22f038..1fe71065f47 100644 --- a/tests/integration_tests/reports/api_tests.py +++ b/tests/integration_tests/reports/api_tests.py @@ -29,6 +29,7 @@ from superset.models.slice import Slice from superset.models.dashboard import Dashboard from superset.models.reports import ( ReportSchedule, + ReportCreationMethodType, ReportRecipients, ReportExecutionLog, ReportScheduleType, @@ -269,6 +270,7 @@ class TestReportSchedulesApi(SupersetTestCase): "changed_on_delta_humanized", "created_by", "created_on", + "creation_method", "crontab", "crontab_humanized", "id", @@ -442,6 +444,7 @@ class TestReportSchedulesApi(SupersetTestCase): "name": "new3", "description": "description", "crontab": "0 9 * * *", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "recipients": [ { "type": ReportRecipientType.EMAIL, @@ -470,6 +473,7 @@ class TestReportSchedulesApi(SupersetTestCase): assert created_model.crontab == report_schedule_data["crontab"] assert created_model.chart.id == report_schedule_data["chart"] assert created_model.database.id == report_schedule_data["database"] + assert created_model.creation_method == report_schedule_data["creation_method"] # Rollback changes db.session.delete(created_model) db.session.commit() @@ -487,6 +491,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.ALERT, "name": "name3", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "chart": chart.id, "database": example_db.id, @@ -503,6 +508,7 @@ class TestReportSchedulesApi(SupersetTestCase): "name": "name3", "description": "description", "crontab": "0 9 * * *", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "chart": chart.id, } uri = "api/v1/report/" @@ -532,6 +538,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.REPORT, "name": "name3", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "chart": chart.id, "database": example_db.id, @@ -545,6 +552,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.ALERT, "name": "new3", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "recipients": [ { @@ -569,6 +577,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.ALERT, "name": "new3", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "recipients": [ { @@ -592,6 +601,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.ALERT, "name": "new3", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "recipients": [ { @@ -617,6 +627,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.ALERT, "name": "new4", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "recipients": [ { @@ -642,6 +653,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.ALERT, "name": "new5", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "recipients": [ { @@ -678,6 +690,7 @@ class TestReportSchedulesApi(SupersetTestCase): "name": "new3", "description": "description", "crontab": "0 9 * * *", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "chart": chart.id, "dashboard": dashboard.id, "database": example_db.id, @@ -701,6 +714,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.ALERT, "name": "new3", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "chart": chart.id, } @@ -726,6 +740,7 @@ class TestReportSchedulesApi(SupersetTestCase): "type": ReportScheduleType.ALERT, "name": "new3", "description": "description", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "crontab": "0 9 * * *", "chart": chart_max_id + 1, "database": database_max_id + 1, @@ -748,6 +763,7 @@ class TestReportSchedulesApi(SupersetTestCase): "name": "new3", "description": "description", "crontab": "0 9 * * *", + "creation_method": ReportCreationMethodType.ALERTS_REPORTS, "dashboard": dashboard_max_id + 1, "database": examples_db.id, } @@ -757,6 +773,82 @@ class TestReportSchedulesApi(SupersetTestCase): data = json.loads(rv.data.decode("utf-8")) assert data == {"message": {"dashboard": "Dashboard does not exist"}} + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") + # TODO (AAfghahi): I am going to enable this when the report schedule feature is fully finished + # def test_create_report_schedule_no_creation_method(self): + # """ + # ReportSchedule Api: Test create report schedule + # """ + # self.login(username="admin") + + # chart = db.session.query(Slice).first() + # example_db = get_example_database() + # report_schedule_data = { + # "type": ReportScheduleType.ALERT, + # "name": "new3", + # "description": "description", + # "crontab": "0 9 * * *", + # "recipients": [ + # { + # "type": ReportRecipientType.EMAIL, + # "recipient_config_json": {"target": "target@superset.org"}, + # }, + # { + # "type": ReportRecipientType.SLACK, + # "recipient_config_json": {"target": "channel"}, + # }, + # ], + # "grace_period": 14400, + # "working_timeout": 3600, + # "chart": chart.id, + # "database": example_db.id, + # } + # uri = "api/v1/report/" + # rv = self.client.post(uri, json=report_schedule_data) + # response = json.loads(rv.data.decode("utf-8")) + # assert response == { + # "message": {"creation_method": ["Missing data for required field."]} + # } + # assert rv.status_code == 400 + + @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices") + def test_create_report_schedule_invalid_creation_method(self): + """ + ReportSchedule API: Test create report schedule + """ + self.login(username="admin") + + chart = db.session.query(Slice).first() + example_db = get_example_database() + report_schedule_data = { + "type": ReportScheduleType.ALERT, + "name": "new3", + "description": "description", + "creation_method": "BAD_CREATION_METHOD", + "crontab": "0 9 * * *", + "recipients": [ + { + "type": ReportRecipientType.EMAIL, + "recipient_config_json": {"target": "target@superset.org"}, + }, + { + "type": ReportRecipientType.SLACK, + "recipient_config_json": {"target": "channel"}, + }, + ], + "grace_period": 14400, + "working_timeout": 3600, + "chart": chart.id, + "database": example_db.id, + } + uri = "api/v1/report/" + rv = self.client.post(uri, json=report_schedule_data) + response = json.loads(rv.data.decode("utf-8")) + assert response == { + "message": {"creation_method": ["Invalid enum value BAD_CREATION_METHOD"]} + } + assert rv.status_code == 400 + @pytest.mark.usefixtures("create_report_schedules") def test_update_report_schedule(self): """