feat: add force option to report screenshots (#17853)
* Update existing tests * Add backend test * feat: add force option to report screenshots * Add tests * Rebase fixes * Do not force screenshot on dashboard alerts
This commit is contained in:
parent
cb0b7a2284
commit
2cd8054358
|
|
@ -71,6 +71,7 @@ export interface ReportObject {
|
|||
validator_type: string;
|
||||
working_timeout: number;
|
||||
creation_method: string;
|
||||
force_screenshot: boolean;
|
||||
}
|
||||
|
||||
interface ChartObject {
|
||||
|
|
@ -227,6 +228,7 @@ const ReportModal: FunctionComponent<ReportProps> = ({
|
|||
active: true,
|
||||
report_format: currentReport?.report_format || defaultNotificationFormat,
|
||||
timezone: currentReport?.timezone,
|
||||
force_screenshot: false,
|
||||
};
|
||||
|
||||
if (isEditMode) {
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ const DEFAULT_ALERT = {
|
|||
sql: '',
|
||||
validator_config_json: {},
|
||||
validator_type: '',
|
||||
force_screenshot: false,
|
||||
grace_period: undefined,
|
||||
};
|
||||
|
||||
|
|
@ -512,6 +513,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
|
|||
const data: any = {
|
||||
...currentAlert,
|
||||
type: isReport ? 'Report' : 'Alert',
|
||||
force_screenshot: contentType === 'chart' && !isReport ? 'true' : 'false',
|
||||
validator_type: conditionNotNull ? 'not null' : 'operator',
|
||||
validator_config_json: conditionNotNull
|
||||
? {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
# 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.
|
||||
"""Add force_screenshot to alerts/reports
|
||||
|
||||
Revision ID: bb38f40aa3ff
|
||||
Revises: 31bb738bd1d2
|
||||
Create Date: 2021-12-10 19:25:29.802949
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "bb38f40aa3ff"
|
||||
down_revision = "31bb738bd1d2"
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from superset import db
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class ReportSchedule(Base):
|
||||
__tablename__ = "report_schedule"
|
||||
|
||||
id = sa.Column(sa.Integer, primary_key=True)
|
||||
type = sa.Column(sa.String(50), nullable=False)
|
||||
force_screenshot = sa.Column(sa.Boolean, default=False)
|
||||
|
||||
|
||||
def upgrade():
|
||||
with op.batch_alter_table("report_schedule") as batch_op:
|
||||
batch_op.add_column(sa.Column("force_screenshot", sa.Boolean(), default=False))
|
||||
|
||||
bind = op.get_bind()
|
||||
session = db.Session(bind=bind)
|
||||
|
||||
for report in session.query(ReportSchedule).all():
|
||||
# Update existing alerts that send chart screenshots so that the cache is
|
||||
# bypassed. We don't turn this one for dashboards because (1) it's currently
|
||||
# not supported but also because (2) it can be very expensive.
|
||||
report.force_screenshot = report.type == "Alert" and report.chart_id is not None
|
||||
|
||||
session.commit()
|
||||
|
||||
|
||||
def downgrade():
|
||||
with op.batch_alter_table("report_schedule") as batch_op:
|
||||
batch_op.drop_column("force_screenshot")
|
||||
|
|
@ -148,6 +148,9 @@ class ReportSchedule(Model, AuditMixinNullable):
|
|||
# Store the selected dashboard tabs etc.
|
||||
extra = Column(Text, default="{}")
|
||||
|
||||
# (Reports) When generating a screenshot, bypass the cache?
|
||||
force_screenshot = Column(Boolean, default=False)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self.name)
|
||||
|
||||
|
|
|
|||
|
|
@ -148,13 +148,7 @@ class BaseReportState:
|
|||
"""
|
||||
# For alerts we always want to send a fresh screenshot, bypassing
|
||||
# the cache.
|
||||
# TODO (betodealmeida): allow to specify per report if users want
|
||||
# to bypass the cache as well.
|
||||
force = (
|
||||
"true"
|
||||
if self._report_schedule.type == ReportScheduleType.ALERT
|
||||
else "false"
|
||||
)
|
||||
force = "true" if self._report_schedule.force_screenshot else "false"
|
||||
|
||||
if self._report_schedule.chart:
|
||||
if result_format in {
|
||||
|
|
@ -181,7 +175,7 @@ class BaseReportState:
|
|||
user_friendly=user_friendly,
|
||||
dashboard_id_or_slug=self._report_schedule.dashboard_id,
|
||||
standalone=DashboardStandaloneMode.REPORT.value,
|
||||
force=force,
|
||||
# force=force, TODO (betodealmeida) implement this
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ class ReportSchedulePostSchema(Schema):
|
|||
default=ReportDataFormat.VISUALIZATION,
|
||||
validate=validate.OneOf(choices=tuple(key.value for key in ReportDataFormat)),
|
||||
)
|
||||
force_screenshot = fields.Boolean(default=False)
|
||||
|
||||
@validates_schema
|
||||
def validate_report_references( # pylint: disable=unused-argument,no-self-use
|
||||
|
|
@ -292,3 +293,4 @@ class ReportSchedulePutSchema(Schema):
|
|||
default=ReportDataFormat.VISUALIZATION,
|
||||
validate=validate.OneOf(choices=tuple(key.value for key in ReportDataFormat)),
|
||||
)
|
||||
force_screenshot = fields.Boolean(default=False)
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ def create_report_notification(
|
|||
grace_period: Optional[int] = None,
|
||||
report_format: Optional[ReportDataFormat] = None,
|
||||
name: Optional[str] = None,
|
||||
force_screenshot: bool = False,
|
||||
) -> ReportSchedule:
|
||||
report_type = report_type or ReportScheduleType.REPORT
|
||||
target = email_target or slack_channel
|
||||
|
|
@ -174,6 +175,7 @@ def create_report_notification(
|
|||
validator_config_json=validator_config_json,
|
||||
grace_period=grace_period,
|
||||
report_format=report_format or ReportDataFormat.VISUALIZATION,
|
||||
force_screenshot=force_screenshot,
|
||||
)
|
||||
return report_schedule
|
||||
|
||||
|
|
@ -218,6 +220,18 @@ def create_report_email_chart():
|
|||
cleanup_report_schedule(report_schedule)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def create_report_email_chart_force_screenshot():
|
||||
with app.app_context():
|
||||
chart = db.session.query(Slice).first()
|
||||
report_schedule = create_report_notification(
|
||||
email_target="target@email.com", chart=chart, force_screenshot=True
|
||||
)
|
||||
yield report_schedule
|
||||
|
||||
cleanup_report_schedule(report_schedule)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def create_report_email_chart_with_csv():
|
||||
with app.app_context():
|
||||
|
|
@ -480,6 +494,7 @@ def create_alert_email_chart(request):
|
|||
validator_config_json=param_config[request.param][
|
||||
"validator_config_json"
|
||||
],
|
||||
force_screenshot=True,
|
||||
)
|
||||
yield report_schedule
|
||||
|
||||
|
|
@ -678,6 +693,49 @@ def test_email_chart_report_schedule(
|
|||
assert_log(ReportState.SUCCESS)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(
|
||||
"load_birth_names_dashboard_with_slices",
|
||||
"create_report_email_chart_force_screenshot",
|
||||
)
|
||||
@patch("superset.reports.notifications.email.send_email_smtp")
|
||||
@patch("superset.utils.screenshots.ChartScreenshot.get_screenshot")
|
||||
def test_email_chart_report_schedule_force_screenshot(
|
||||
screenshot_mock, email_mock, create_report_email_chart_force_screenshot,
|
||||
):
|
||||
"""
|
||||
ExecuteReport Command: Test chart email report schedule with screenshot
|
||||
|
||||
In this test ``force_screenshot`` is true, and the screenshot URL should
|
||||
reflect that.
|
||||
"""
|
||||
# setup screenshot mock
|
||||
screenshot_mock.return_value = SCREENSHOT_FILE
|
||||
|
||||
with freeze_time("2020-01-01T00:00:00Z"):
|
||||
AsyncExecuteReportScheduleCommand(
|
||||
TEST_ID, create_report_email_chart_force_screenshot.id, datetime.utcnow()
|
||||
).run()
|
||||
|
||||
notification_targets = get_target_from_report_schedule(
|
||||
create_report_email_chart_force_screenshot
|
||||
)
|
||||
# assert that the link sent is correct
|
||||
assert (
|
||||
'<a href="http://0.0.0.0:8080/superset/explore/?'
|
||||
"form_data=%7B%22slice_id%22%3A+"
|
||||
f"{create_report_email_chart_force_screenshot.chart.id}%7D&"
|
||||
'standalone=true&force=true">Explore in Superset</a>'
|
||||
in email_mock.call_args[0][2]
|
||||
)
|
||||
# Assert the email smtp address
|
||||
assert email_mock.call_args[0][0] == notification_targets[0]
|
||||
# Assert the email inline screenshot
|
||||
smtp_images = email_mock.call_args[1]["images"]
|
||||
assert smtp_images[list(smtp_images.keys())[0]] == SCREENSHOT_FILE
|
||||
# Assert logs are correct
|
||||
assert_log(ReportState.SUCCESS)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures(
|
||||
"load_birth_names_dashboard_with_slices", "create_alert_email_chart"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ def insert_report_schedule(
|
|||
recipients: Optional[List[ReportRecipients]] = None,
|
||||
report_format: Optional[ReportDataFormat] = None,
|
||||
logs: Optional[List[ReportExecutionLog]] = None,
|
||||
force_screenshot: bool = False,
|
||||
) -> ReportSchedule:
|
||||
owners = owners or []
|
||||
recipients = recipients or []
|
||||
|
|
@ -75,6 +76,7 @@ def insert_report_schedule(
|
|||
logs=logs,
|
||||
last_state=last_state,
|
||||
report_format=report_format,
|
||||
force_screenshot=force_screenshot,
|
||||
)
|
||||
db.session.add(report_schedule)
|
||||
db.session.commit()
|
||||
|
|
|
|||
Loading…
Reference in New Issue