123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- # 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.
- import enum
- from typing import Optional, Type
- import simplejson as json
- from croniter import croniter
- from flask import flash, g
- from flask_appbuilder import expose
- from flask_appbuilder.models.sqla.interface import SQLAInterface
- from flask_appbuilder.security.decorators import has_access
- from flask_babel import lazy_gettext as _
- from wtforms import BooleanField, StringField
- from superset import db, security_manager
- from superset.constants import RouteMethod
- from superset.exceptions import SupersetException
- from superset.models.dashboard import Dashboard
- from superset.models.schedules import (
- DashboardEmailSchedule,
- ScheduleType,
- SliceEmailSchedule,
- )
- from superset.models.slice import Slice
- from superset.tasks.schedules import schedule_email_report
- from superset.utils.core import get_email_address_list, json_iso_dttm_ser
- from superset.views.core import json_success
- from .base import DeleteMixin, SupersetModelView
- class EmailScheduleView(
- SupersetModelView, DeleteMixin
- ): # pylint: disable=too-many-ancestors
- include_route_methods = RouteMethod.CRUD_SET
- _extra_data = {"test_email": False, "test_email_recipients": None}
- schedule_type: Optional[Type] = None
- schedule_type_model: Optional[Type] = None
- page_size = 20
- add_exclude_columns = [
- "user",
- "created_on",
- "changed_on",
- "created_by",
- "changed_by",
- ]
- edit_exclude_columns = add_exclude_columns
- description_columns = {
- "deliver_as_group": "If enabled, send a single email to all "
- "recipients (in email/To: field)",
- "crontab": "Unix style crontab schedule to deliver emails. "
- "Changes to schedules reflect in one hour.",
- "delivery_type": "Indicates how the rendered content is delivered",
- }
- add_form_extra_fields = {
- "test_email": BooleanField(
- "Send Test Email",
- default=False,
- description="If enabled, we send a test mail on create / update",
- ),
- "test_email_recipients": StringField(
- "Test Email Recipients",
- default=None,
- description="List of recipients to send test email to. "
- "If empty, we send it to the original recipients",
- ),
- }
- edit_form_extra_fields = add_form_extra_fields
- def process_form(self, form, is_created):
- if form.test_email_recipients.data:
- test_email_recipients = form.test_email_recipients.data.strip()
- else:
- test_email_recipients = None
- self._extra_data["test_email"] = form.test_email.data
- self._extra_data["test_email_recipients"] = test_email_recipients
- def pre_add(self, item):
- try:
- recipients = get_email_address_list(item.recipients)
- item.recipients = ", ".join(recipients)
- except Exception:
- raise SupersetException("Invalid email list")
- item.user = item.user or g.user
- if not croniter.is_valid(item.crontab):
- raise SupersetException("Invalid crontab format")
- def pre_update(self, item):
- self.pre_add(item)
- def post_add(self, item):
- # Schedule a test mail if the user requested for it.
- if self._extra_data["test_email"]:
- recipients = self._extra_data["test_email_recipients"] or item.recipients
- args = (self.schedule_type, item.id)
- kwargs = dict(recipients=recipients)
- schedule_email_report.apply_async(args=args, kwargs=kwargs)
- # Notify the user that schedule changes will be activate only in the
- # next hour
- if item.active:
- flash("Schedule changes will get applied in one hour", "warning")
- def post_update(self, item):
- self.post_add(item)
- @has_access
- @expose("/fetch/<int:item_id>/", methods=["GET"])
- def fetch_schedules(self, item_id):
- query = db.session.query(self.datamodel.obj)
- query = query.join(self.schedule_type_model).filter(
- self.schedule_type_model.id == item_id
- )
- schedules = []
- for schedule in query.all():
- info = {"schedule": schedule.id}
- for col in self.list_columns + self.add_exclude_columns:
- info[col] = getattr(schedule, col)
- if isinstance(info[col], enum.Enum):
- info[col] = info[col].name
- elif isinstance(info[col], security_manager.user_model):
- info[col] = info[col].username
- info["user"] = schedule.user.username
- info[self.schedule_type] = getattr(schedule, self.schedule_type).id
- schedules.append(info)
- return json_success(json.dumps(schedules, default=json_iso_dttm_ser))
- class DashboardEmailScheduleView(
- EmailScheduleView
- ): # pylint: disable=too-many-ancestors
- schedule_type = ScheduleType.dashboard.value
- schedule_type_model = Dashboard
- add_title = _("Schedule Email Reports for Dashboards")
- edit_title = add_title
- list_title = _("Manage Email Reports for Dashboards")
- datamodel = SQLAInterface(DashboardEmailSchedule)
- order_columns = ["user", "dashboard", "created_on"]
- list_columns = [
- "dashboard",
- "active",
- "crontab",
- "user",
- "deliver_as_group",
- "delivery_type",
- ]
- add_columns = [
- "dashboard",
- "active",
- "crontab",
- "recipients",
- "deliver_as_group",
- "delivery_type",
- "test_email",
- "test_email_recipients",
- ]
- edit_columns = add_columns
- search_columns = [
- "dashboard",
- "active",
- "user",
- "deliver_as_group",
- "delivery_type",
- ]
- label_columns = {
- "dashboard": _("Dashboard"),
- "created_on": _("Created On"),
- "changed_on": _("Changed On"),
- "user": _("User"),
- "active": _("Active"),
- "crontab": _("Crontab"),
- "recipients": _("Recipients"),
- "deliver_as_group": _("Deliver As Group"),
- "delivery_type": _("Delivery Type"),
- }
- def pre_add(self, item):
- if item.dashboard is None:
- raise SupersetException("Dashboard is mandatory")
- super(DashboardEmailScheduleView, self).pre_add(item)
- class SliceEmailScheduleView(EmailScheduleView): # pylint: disable=too-many-ancestors
- schedule_type = ScheduleType.slice.value
- schedule_type_model = Slice
- add_title = _("Schedule Email Reports for Charts")
- edit_title = add_title
- list_title = _("Manage Email Reports for Charts")
- datamodel = SQLAInterface(SliceEmailSchedule)
- order_columns = ["user", "slice", "created_on"]
- list_columns = [
- "slice",
- "active",
- "crontab",
- "user",
- "deliver_as_group",
- "delivery_type",
- "email_format",
- ]
- add_columns = [
- "slice",
- "active",
- "crontab",
- "recipients",
- "deliver_as_group",
- "delivery_type",
- "email_format",
- "test_email",
- "test_email_recipients",
- ]
- edit_columns = add_columns
- search_columns = [
- "slice",
- "active",
- "user",
- "deliver_as_group",
- "delivery_type",
- "email_format",
- ]
- label_columns = {
- "slice": _("Chart"),
- "created_on": _("Created On"),
- "changed_on": _("Changed On"),
- "user": _("User"),
- "active": _("Active"),
- "crontab": _("Crontab"),
- "recipients": _("Recipients"),
- "deliver_as_group": _("Deliver As Group"),
- "delivery_type": _("Delivery Type"),
- "email_format": _("Email Format"),
- }
- def pre_add(self, item):
- if item.slice is None:
- raise SupersetException("Slice is mandatory")
- super(SliceEmailScheduleView, self).pre_add(item)
|