You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2023/06/28 23:05:01 UTC

[superset] branch customize_screenshot_width updated (5afc40aebd -> eb07418c78)

This is an automated email from the ASF dual-hosted git repository.

beto pushed a change to branch customize_screenshot_width
in repository https://gitbox.apache.org/repos/asf/superset.git


 discard 5afc40aebd Add some constants
     add 0ddc0a6738 chore: remove marshmallow-enum dependency and bump FAB (#24499)
     add 3f17945745 fix(charts): big-number display broken in echarts (#24492)
     add 750113441c chore: update d3-colors (#24505)
     new 2db835d862 Add some constants
     new 9193109e5a Update reports model
     new eb07418c78 Add affordances

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (5afc40aebd)
            \
             N -- N -- N   refs/heads/customize_screenshot_width (eb07418c78)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 requirements/base.txt                              |  13 +--
 requirements/docker.txt                            |   4 +
 requirements/integration.txt                       |  11 ++-
 setup.cfg                                          |   2 +-
 setup.py                                           |   2 +-
 superset-frontend/jest.config.js                   |   2 +-
 superset-frontend/package-lock.json                |  53 +++++++---
 superset-frontend/package.json                     |   3 +-
 .../src/chart/models/ChartMetadata.ts              |   6 ++
 .../src/BigNumber/BigNumberTotal/index.ts          |   9 +-
 .../src/BigNumber/BigNumberWithTrendline/index.ts  |   9 +-
 .../plugin-chart-echarts/src/BoxPlot/index.ts      |   9 +-
 .../plugin-chart-echarts/src/Funnel/index.ts       |   9 +-
 .../plugin-chart-echarts/src/Gauge/index.ts        |   9 +-
 .../plugin-chart-echarts/src/Graph/index.ts        |   9 +-
 .../src/MixedTimeseries/index.ts                   |   9 +-
 .../plugins/plugin-chart-echarts/src/Pie/index.ts  |   9 +-
 .../plugin-chart-echarts/src/Radar/index.ts        |   9 +-
 .../plugin-chart-echarts/src/Sunburst/index.ts     |   9 +-
 .../src/Timeseries/Area/index.ts                   |   9 +-
 .../src/Timeseries/Regular/Bar/index.ts            |   9 +-
 .../src/Timeseries/Regular/Line/index.ts           |   9 +-
 .../src/Timeseries/Regular/Scatter/index.ts        |   9 +-
 .../src/Timeseries/Regular/SmoothLine/index.ts     |   9 +-
 .../src/Timeseries/Step/index.ts                   |   9 +-
 .../plugin-chart-echarts/src/Timeseries/index.ts   |   9 +-
 .../plugins/plugin-chart-echarts/src/Tree/index.ts |   9 +-
 .../plugin-chart-echarts/src/Treemap/index.ts      |   9 +-
 .../plugins/plugin-chart-echarts/src/types.ts      |  19 ++++
 .../plugin-chart-echarts/test/index.test.ts        | 108 +++++++++++++++++++--
 .../src/components/Chart/chartAction.js            |  19 ++--
 .../src/components/ReportModal/index.tsx           |  23 +++++
 .../src/explore/exploreUtils/exploreUtils.test.jsx |  16 +--
 .../src/explore/exploreUtils/index.js              |  11 ++-
 .../exploreUtils/shouldUseLegacyApi.test.ts        |  11 ++-
 .../src/features/alerts/AlertReportModal.tsx       |  64 ++++++++----
 superset-frontend/src/features/alerts/types.ts     |   3 +-
 superset-frontend/src/reports/types.ts             |   1 +
 superset/charts/schemas.py                         |   9 +-
 superset/config.py                                 |   3 +
 superset/dashboards/schemas.py                     |   3 +-
 superset/databases/schemas.py                      |   5 +-
 ...b0fb85b9a_add_custom_size_columns_to_report.py} |  25 +++--
 superset/reports/api.py                            |   2 +
 superset/reports/models.py                         |   3 +
 superset/reports/schemas.py                        |  57 ++++++++++-
 superset/security/api.py                           |   3 +-
 tests/integration_tests/base_api_tests.py          |   2 +-
 tests/integration_tests/databases/api_tests.py     |  15 ++-
 .../db_engine_specs/postgres_tests.py              |   1 -
 tests/integration_tests/reports/api_tests.py       |   6 +-
 .../unit_tests/db_engine_specs/test_databricks.py  |   1 -
 52 files changed, 482 insertions(+), 195 deletions(-)
 copy superset/migrations/versions/{2020-12-03_10-11_5daced1f0e76_reports_add_working_timeout_column.py => 2023-06-27_16-54_8e5b0fb85b9a_add_custom_size_columns_to_report.py} (66%)


[superset] 03/03: Add affordances

Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch customize_screenshot_width
in repository https://gitbox.apache.org/repos/asf/superset.git

commit eb07418c78b2368592a551191b26e8ad7ba14563
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Jun 28 16:03:38 2023 -0700

    Add affordances
---
 .../src/components/ReportModal/index.tsx           | 23 ++++++++
 .../src/features/alerts/AlertReportModal.tsx       | 64 ++++++++++++++++------
 superset-frontend/src/features/alerts/types.ts     |  3 +-
 superset-frontend/src/reports/types.ts             |  1 +
 superset/config.py                                 |  3 +
 superset/reports/api.py                            |  2 +
 superset/reports/schemas.py                        | 52 +++++++++++++++++-
 7 files changed, 128 insertions(+), 20 deletions(-)

diff --git a/superset-frontend/src/components/ReportModal/index.tsx b/superset-frontend/src/components/ReportModal/index.tsx
index 5bf4c2c4d2..cb229f1f3f 100644
--- a/superset-frontend/src/components/ReportModal/index.tsx
+++ b/superset-frontend/src/components/ReportModal/index.tsx
@@ -41,6 +41,7 @@ import {
   NOTIFICATION_FORMATS,
 } from 'src/reports/types';
 import { reportSelector } from 'src/views/CRUD/hooks';
+import { TRANSLATIONS } from 'src/features/alerts/AlertReportModal';
 import { CreationMethod } from './HeaderReportDropdown';
 import {
   antDErrorAlertStyles,
@@ -170,6 +171,7 @@ function ReportModal({
       type: 'Report',
       active: true,
       force_screenshot: false,
+      custom_width: currentReport.custom_width,
       creation_method: creationMethod,
       dashboard: dashboardId,
       chart: chart?.id,
@@ -257,6 +259,26 @@ function ReportModal({
       </div>
     </>
   );
+  const renderCustomWidthSection = (
+    <div>
+      <div className="control-label">
+        {TRANSLATIONS.CUSTOM_SCREENSHOT_WIDTH_TEXT}
+      </div>
+      <div className="input-container">
+        <input
+          type="number"
+          name="custom_width"
+          value={currentReport?.custom_width || ''}
+          placeholder={TRANSLATIONS.CUSTOM_SCREENSHOT_WIDTH_PLACEHOLDER_TEXT}
+          onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+            setCurrentReport({
+              custom_width: parseInt(event.target.value, 10) || null,
+            });
+          }}
+        />
+      </div>
+    </div>
+  );
 
   return (
     <StyledModal
@@ -331,6 +353,7 @@ function ReportModal({
           }}
         />
         {isChart && renderMessageContentSection}
+        {(!isChart || !isTextBasedChart) && renderCustomWidthSection}
       </StyledBottomSection>
       {currentReport.error && (
         <Alert
diff --git a/superset-frontend/src/features/alerts/AlertReportModal.tsx b/superset-frontend/src/features/alerts/AlertReportModal.tsx
index 8ff344c97e..0aeae9103e 100644
--- a/superset-frontend/src/features/alerts/AlertReportModal.tsx
+++ b/superset-frontend/src/features/alerts/AlertReportModal.tsx
@@ -370,7 +370,7 @@ interface NotificationMethodAddProps {
   onClick: () => void;
 }
 
-const TRANSLATIONS = {
+export const TRANSLATIONS = {
   ADD_NOTIFICATION_METHOD_TEXT: t('Add notification method'),
   ADD_DELIVERY_METHOD_TEXT: t('Add delivery method'),
   SAVE_TEXT: t('Save'),
@@ -406,7 +406,9 @@ const TRANSLATIONS = {
   SEND_AS_PNG_TEXT: t('Send as PNG'),
   SEND_AS_CSV_TEXT: t('Send as CSV'),
   SEND_AS_TEXT: t('Send as text'),
-  IGNORE_CACHE_TEXT: t('Ignore cache when generating screenshot'),
+  IGNORE_CACHE_TEXT: t('Ignore cache when generating report'),
+  CUSTOM_SCREENSHOT_WIDTH_TEXT: t('Screenshot width'),
+  CUSTOM_SCREENSHOT_WIDTH_PLACEHOLDER_TEXT: t('Input custom width in pixels'),
   NOTIFICATION_METHOD_TEXT: t('Notification method'),
 };
 
@@ -466,6 +468,14 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
   );
   const [forceScreenshot, setForceScreenshot] = useState<boolean>(false);
 
+  const [isScreenshot, setIsScreenshot] = useState<boolean>(false);
+  useEffect(() => {
+    setIsScreenshot(
+      contentType === 'dashboard' ||
+        (contentType === 'chart' && reportFormat === 'PNG'),
+    );
+  }, [contentType, reportFormat]);
+
   // Dropdown options
   const [conditionNotNull, setConditionNotNull] = useState<boolean>(false);
   const [sourceOptions, setSourceOptions] = useState<MetaObject[]>([]);
@@ -853,12 +863,16 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
     }).then(response => setChartVizType(response.json.result.viz_type));
 
   // Handle input/textarea updates
-  const onTextChange = (
+  const onInputChange = (
     event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>,
   ) => {
     const { target } = event;
+    const value =
+      target.type === 'number'
+        ? parseInt(target.value, 10) || null
+        : target.value;
 
-    updateAlertState(target.name, target.value);
+    updateAlertState(target.name, value);
   };
 
   const onTimeoutVerifyChange = (
@@ -1180,7 +1194,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
                     ? TRANSLATIONS.REPORT_NAME_TEXT
                     : TRANSLATIONS.ALERT_NAME_TEXT
                 }
-                onChange={onTextChange}
+                onChange={onInputChange}
                 css={inputSpacer}
               />
             </div>
@@ -1216,7 +1230,7 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
                 name="description"
                 value={currentAlert ? currentAlert.description || '' : ''}
                 placeholder={TRANSLATIONS.DESCRIPTION_TEXT}
-                onChange={onTextChange}
+                onChange={onInputChange}
                 css={inputSpacer}
               />
             </div>
@@ -1471,18 +1485,34 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
                 </div>
               </>
             )}
-            {(isReport || contentType === 'dashboard') && (
-              <div className="inline-container">
-                <StyledCheckbox
-                  data-test="bypass-cache"
-                  className="checkbox"
-                  checked={forceScreenshot}
-                  onChange={onForceScreenshotChange}
-                >
-                  {TRANSLATIONS.IGNORE_CACHE_TEXT}
-                </StyledCheckbox>
-              </div>
+            {isScreenshot && (
+              <StyledInputContainer>
+                <div className="control-label">
+                  {TRANSLATIONS.CUSTOM_SCREENSHOT_WIDTH_TEXT}
+                </div>
+                <div className="input-container">
+                  <input
+                    type="number"
+                    name="custom_width"
+                    value={currentAlert?.custom_width || ''}
+                    placeholder={
+                      TRANSLATIONS.CUSTOM_SCREENSHOT_WIDTH_PLACEHOLDER_TEXT
+                    }
+                    onChange={onInputChange}
+                  />
+                </div>
+              </StyledInputContainer>
             )}
+            <div className="inline-container">
+              <StyledCheckbox
+                data-test="bypass-cache"
+                className="checkbox"
+                checked={forceScreenshot}
+                onChange={onForceScreenshotChange}
+              >
+                {TRANSLATIONS.IGNORE_CACHE_TEXT}
+              </StyledCheckbox>
+            </div>
             <StyledSectionTitle>
               <h4>{TRANSLATIONS.NOTIFICATION_METHOD_TEXT}</h4>
               <span className="required">*</span>
diff --git a/superset-frontend/src/features/alerts/types.ts b/superset-frontend/src/features/alerts/types.ts
index 36d2b1d35a..34eb7fd261 100644
--- a/superset-frontend/src/features/alerts/types.ts
+++ b/superset-frontend/src/features/alerts/types.ts
@@ -68,10 +68,12 @@ export type AlertObject = {
   created_by?: user;
   created_on?: string;
   crontab?: string;
+  custom_width?: number | null;
   dashboard?: MetaObject;
   dashboard_id?: number;
   database?: MetaObject;
   description?: string;
+  error?: string;
   force_screenshot: boolean;
   grace_period?: number;
   id: number;
@@ -91,7 +93,6 @@ export type AlertObject = {
   };
   validator_type?: string;
   working_timeout?: number;
-  error?: string;
 };
 
 export type LogObject = {
diff --git a/superset-frontend/src/reports/types.ts b/superset-frontend/src/reports/types.ts
index 38cb3865cf..b67a7bac7b 100644
--- a/superset-frontend/src/reports/types.ts
+++ b/superset-frontend/src/reports/types.ts
@@ -56,5 +56,6 @@ export interface ReportObject {
   working_timeout: number;
   creation_method: string;
   force_screenshot: boolean;
+  custom_width?: number | null;
   error?: string;
 }
diff --git a/superset/config.py b/superset/config.py
index d62003991a..daefcb1cc0 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -1271,6 +1271,9 @@ ALERT_REPORTS_NOTIFICATION_DRY_RUN = False
 # Max tries to run queries to prevent false errors caused by transient errors
 # being returned to users. Set to a value >1 to enable retries.
 ALERT_REPORTS_QUERY_EXECUTION_MAX_TRIES = 1
+# Custom width for screenshots
+ALERT_REPORTS_MIN_CUSTOM_SCREENSHOT_WIDTH = 600
+ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH = 2400
 
 # A custom prefix to use on all Alerts & Reports emails
 EMAIL_REPORTS_SUBJECT_PREFIX = "[Report] "
diff --git a/superset/reports/api.py b/superset/reports/api.py
index 125a3e6763..3686ab74bd 100644
--- a/superset/reports/api.py
+++ b/superset/reports/api.py
@@ -93,6 +93,7 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
         "context_markdown",
         "creation_method",
         "crontab",
+        "custom_width",
         "dashboard.dashboard_title",
         "dashboard.id",
         "database.database_name",
@@ -159,6 +160,7 @@ class ReportScheduleRestApi(BaseSupersetModelRestApi):
         "context_markdown",
         "creation_method",
         "crontab",
+        "custom_width",
         "dashboard",
         "database",
         "description",
diff --git a/superset/reports/schemas.py b/superset/reports/schemas.py
index fbe681be36..7bdbf34f12 100644
--- a/superset/reports/schemas.py
+++ b/superset/reports/schemas.py
@@ -17,8 +17,9 @@
 from typing import Any, Union
 
 from croniter import croniter
+from flask import current_app
 from flask_babel import gettext as _
-from marshmallow import fields, Schema, validate, validates_schema
+from marshmallow import fields, Schema, validate, validates, validates_schema
 from marshmallow.validate import Length, Range, ValidationError
 from pytz import all_timezones
 
@@ -208,10 +209,34 @@ class ReportSchedulePostSchema(Schema):
         dump_default=None,
     )
     force_screenshot = fields.Boolean(dump_default=False)
+    custom_width = fields.Integer(
+        metadata={
+            "description": _("Custom width of the screenshot in pixels"),
+            "example": 1000,
+        },
+        allow_none=True,
+        required=False,
+        default=None,
+    )
+
+    @validates("custom_width")
+    def validate_custom_width(self, value: int) -> None:  # pylint: disable=no-self-use
+        min_width = current_app.config["ALERT_REPORTS_MIN_CUSTOM_SCREENSHOT_WIDTH"]
+        max_width = current_app.config["ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH"]
+        if not min_width <= value <= max_width:
+            raise ValidationError(
+                _(
+                    "Screenshot width must be between %(min)spx and %(max)spx",
+                    min=min_width,
+                    max=max_width,
+                )
+            )
 
     @validates_schema
     def validate_report_references(  # pylint: disable=unused-argument,no-self-use
-        self, data: dict[str, Any], **kwargs: Any
+        self,
+        data: dict[str, Any],
+        **kwargs: Any,
     ) -> None:
         if data["type"] == ReportScheduleType.REPORT:
             if "database" in data:
@@ -307,3 +332,26 @@ class ReportSchedulePutSchema(Schema):
     )
     extra = fields.Dict(dump_default=None)
     force_screenshot = fields.Boolean(dump_default=False)
+
+    custom_width = fields.Integer(
+        metadata={
+            "description": _("Custom width of the screenshot in pixels"),
+            "example": 1000,
+        },
+        allow_none=True,
+        required=False,
+        default=None,
+    )
+
+    @validates("custom_width")
+    def validate_custom_width(self, value: int) -> None:  # pylint: disable=no-self-use
+        min_width = current_app.config["ALERT_REPORTS_MIN_CUSTOM_SCREENSHOT_WIDTH"]
+        max_width = current_app.config["ALERT_REPORTS_MAX_CUSTOM_SCREENSHOT_WIDTH"]
+        if not min_width <= value <= max_width:
+            raise ValidationError(
+                _(
+                    "Screenshot width must be between %(min)spx and %(max)spx",
+                    min=min_width,
+                    max=max_width,
+                )
+            )


[superset] 01/03: Add some constants

Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch customize_screenshot_width
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 2db835d862129a6144e017d6a0b3451928d3de9f
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Mon Jun 26 12:54:04 2023 -0700

    Add some constants
---
 superset/charts/api.py        |  4 ++--
 superset/utils/screenshots.py | 18 ++++++++++++------
 2 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/superset/charts/api.py b/superset/charts/api.py
index c87b7bdda8..e8ccfa0fff 100644
--- a/superset/charts/api.py
+++ b/superset/charts/api.py
@@ -82,7 +82,7 @@ from superset.extensions import event_logger
 from superset.models.slice import Slice
 from superset.tasks.thumbnails import cache_chart_thumbnail
 from superset.tasks.utils import get_current_user
-from superset.utils.screenshots import ChartScreenshot
+from superset.utils.screenshots import ChartScreenshot, DEFAULT_CHART_WINDOW_SIZE
 from superset.utils.urls import get_url_path
 from superset.views.base_api import (
     BaseSupersetModelRestApi,
@@ -573,7 +573,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
               $ref: '#/components/responses/500'
         """
         rison_dict = kwargs["rison"]
-        window_size = rison_dict.get("window_size") or (800, 600)
+        window_size = rison_dict.get("window_size") or DEFAULT_CHART_WINDOW_SIZE
 
         # Don't shrink the image if thumb_size is not specified
         thumb_size = rison_dict.get("thumb_size") or window_size
diff --git a/superset/utils/screenshots.py b/superset/utils/screenshots.py
index 5c699e9e19..2743f85195 100644
--- a/superset/utils/screenshots.py
+++ b/superset/utils/screenshots.py
@@ -33,6 +33,12 @@ from superset.utils.webdriver import (
 
 logger = logging.getLogger(__name__)
 
+DEFAULT_SCREENSHOT_WINDOW_SIZE = 800, 600
+DEFAULT_SCREENSHOT_THUMBNAIL_SIZE = 400, 300
+DEFAULT_CHART_WINDOW_SIZE = DEFAULT_CHART_THUMBNAIL_SIZE = 800, 600
+DEFAULT_DASHBOARD_WINDOW_SIZE = 1600, 1200
+DEFAULT_DASHBOARD_THUMBNAIL_SIZE = 800, 600
+
 try:
     from PIL import Image
 except ModuleNotFoundError:
@@ -47,8 +53,8 @@ class BaseScreenshot:
     driver_type = current_app.config["WEBDRIVER_TYPE"]
     thumbnail_type: str = ""
     element: str = ""
-    window_size: WindowSize = (800, 600)
-    thumb_size: WindowSize = (400, 300)
+    window_size: WindowSize = DEFAULT_SCREENSHOT_WINDOW_SIZE
+    thumb_size: WindowSize = DEFAULT_SCREENSHOT_THUMBNAIL_SIZE
 
     def __init__(self, url: str, digest: str):
         self.digest: str = digest
@@ -216,8 +222,8 @@ class ChartScreenshot(BaseScreenshot):
             standalone=ChartStandaloneMode.HIDE_NAV.value,
         )
         super().__init__(url, digest)
-        self.window_size = window_size or (800, 600)
-        self.thumb_size = thumb_size or (800, 600)
+        self.window_size = window_size or DEFAULT_CHART_WINDOW_SIZE
+        self.thumb_size = thumb_size or DEFAULT_CHART_THUMBNAIL_SIZE
 
 
 class DashboardScreenshot(BaseScreenshot):
@@ -239,5 +245,5 @@ class DashboardScreenshot(BaseScreenshot):
         )
 
         super().__init__(url, digest)
-        self.window_size = window_size or (1600, 1200)
-        self.thumb_size = thumb_size or (800, 600)
+        self.window_size = window_size or DEFAULT_DASHBOARD_WINDOW_SIZE
+        self.thumb_size = thumb_size or DEFAULT_DASHBOARD_THUMBNAIL_SIZE


[superset] 02/03: Update reports model

Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

beto pushed a commit to branch customize_screenshot_width
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 9193109e5a6053c85a3d481ce30f3dda8f10474c
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Wed Jun 28 10:44:07 2023 -0700

    Update reports model
---
 ...5b0fb85b9a_add_custom_size_columns_to_report.py | 46 ++++++++++++++++++++++
 superset/reports/models.py                         |  3 ++
 2 files changed, 49 insertions(+)

diff --git a/superset/migrations/versions/2023-06-27_16-54_8e5b0fb85b9a_add_custom_size_columns_to_report.py b/superset/migrations/versions/2023-06-27_16-54_8e5b0fb85b9a_add_custom_size_columns_to_report.py
new file mode 100644
index 0000000000..83185e18c7
--- /dev/null
+++ b/superset/migrations/versions/2023-06-27_16-54_8e5b0fb85b9a_add_custom_size_columns_to_report.py
@@ -0,0 +1,46 @@
+# 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 custom size columns to report schedule
+
+Revision ID: 8e5b0fb85b9a
+Revises: 83e1abbe777f
+Create Date: 2023-06-27 16:54:57.161475
+
+"""
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = "8e5b0fb85b9a"
+down_revision = "83e1abbe777f"
+
+
+def upgrade():
+    op.add_column(
+        "report_schedule",
+        sa.Column("custom_width", sa.Integer(), nullable=True),
+    )
+    op.add_column(
+        "report_schedule",
+        sa.Column("custom_height", sa.Integer(), nullable=True),
+    )
+
+
+def downgrade():
+    op.drop_column("report_schedule", "custom_width")
+    op.drop_column("report_schedule", "custom_height")
diff --git a/superset/reports/models.py b/superset/reports/models.py
index 24d4657b7d..2cbcbe0daa 100644
--- a/superset/reports/models.py
+++ b/superset/reports/models.py
@@ -154,6 +154,9 @@ class ReportSchedule(Model, AuditMixinNullable, ExtraJSONMixin):
     # (Reports) When generating a screenshot, bypass the cache?
     force_screenshot = Column(Boolean, default=False)
 
+    custom_width = Column(Integer, nullable=True)
+    custom_height = Column(Integer, nullable=True)
+
     extra: ReportScheduleExtra  # type: ignore
 
     def __repr__(self) -> str: