Skip to content

feat(reports): add XLSX (Excel) attachments for Alerts & Reports#41424

Open
gr33nak wants to merge 1 commit into
apache:masterfrom
gr33nak:feat/xlsx-report-attachments
Open

feat(reports): add XLSX (Excel) attachments for Alerts & Reports#41424
gr33nak wants to merge 1 commit into
apache:masterfrom
gr33nak:feat/xlsx-report-attachments

Conversation

@gr33nak

@gr33nak gr33nak commented Jun 25, 2026

Copy link
Copy Markdown

SUMMARY

Adds Excel (.xlsx) as an attachment format for chart-based Alerts & Reports, alongside the existing CSV / PNG / PDF / text options. Users can now pick "Send as Excel" in the report/alert modal and receive a formatted spreadsheet
by email or Slack.

This picks up and finalizes the long-stalled #19810, which was closed in Nov 2025 due to inactivity. It resolves the two issues that blocked the original:

  1. Spurious index column — the original generated the workbook by re-parsing the CSV into a DataFrame and writing it locally, which produced an extra index column. This implementation instead fetches the pre-rendered XLSX from the chart data export endpoint (result_format=xlsx), so it reuses the exact same post-processing, include_index logic, and the formula-injection-safe superset.utils.excel.df_to_excel path as a chart's normal "Export to Excel" — no spurious column, and no duplicated logic.
  2. Unified data fetching — per maintainer feedback on the original PR, CSV and XLSX now share a single _get_data(result_format) method instead of separate per-format methods.

Key changes

  • ReportDataFormat: add XLSX + a tabular() helper ({CSV, XLSX}).
  • BaseReportState._get_data(result_format): unified CSV/XLSX byte fetch; adds ReportScheduleXlsx{Failed,Timeout}Error.
  • NotificationContent.xlsx; email attaches <name>.xlsx; Slack v1 + v2 upload xlsx.
  • Frontend: NotificationFormats.XLSX and a "Send as Excel" option in both AlertReportModal and the ReportModal.
  • Docs: list Excel among report attachment formats.

XLSX availability mirrors CSV (chart-based reports only). No new feature flag, config key (the existing EXCEL_EXPORT config is reused), or DB migration is required.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

image

TESTING INSTRUCTIONS

Automated:

  • Unit: pytest tests/unit_tests/commands/report/execute_test.py tests/unit_tests/reports/notifications/
  • Integration: pytest tests/integration_tests/reports/commands_tests.py -k "csv or xlsx"
  • Frontend: npm run test -- AlertReportModal and npm run test -- ReportModal

Manual:

  1. Enable the Alerts & Reports feature and have a worker/beat running.
  2. Create a Report on a chart, set Content format → Send as Excel, add an
    email recipient, and trigger it.
  3. Confirm the email contains a valid <report name>.xlsx that opens in
    Excel/LibreOffice with the expected data and no extra leading index column.
  4. Repeat with a Slack recipient and confirm the .xlsx file is uploaded.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

@github-actions github-actions Bot added the doc Namespace | Anything related to documentation label Jun 25, 2026
@dosubot dosubot Bot added alert-reports Namespace | Anything related to the Alert & Reports feature change:backend Requires changing the backend change:frontend Requires changing the frontend labels Jun 25, 2026
Comment thread tests/unit_tests/reports/notifications/slack_tests.py Outdated
Comment thread superset/reports/models.py
@netlify

netlify Bot commented Jun 25, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit f1c5ee7
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a3d6770a74a3700081a9441
😎 Deploy Preview https://deploy-preview-41424--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review

Copy link
Copy Markdown
Contributor

The suggestion to add a type annotation for the mock_header_data parameter is correct, as it ensures the new test code adheres to the project's typing standards. To resolve this, you should update the function signature in the test file to include the appropriate type hint for the parameter.

Since the file tests/unit_tests/reports/notifications/slack_tests.py is not present in the provided PR context, I cannot implement the fix directly. Please apply the type annotation manually in that file.

Regarding other comments on this PR, there are no additional review comments available in the provided context to evaluate.

Comment thread tests/integration_tests/reports/commands_tests.py Outdated
Comment thread tests/integration_tests/reports/commands_tests.py Outdated
Comment thread tests/integration_tests/reports/commands_tests.py Outdated
Comment thread tests/unit_tests/commands/report/execute_test.py Outdated
Comment thread tests/unit_tests/reports/notifications/email_tests.py
Comment thread docs/admin_docs/configuration/alerts-reports.mdx
Comment thread superset/commands/report/execute.py
Comment thread superset/commands/report/execute.py

@bito-code-review bito-code-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Agent Run #6bd892

Actionable Suggestions - 2
  • docs/admin_docs/configuration/alerts-reports.mdx - 1
  • tests/unit_tests/reports/notifications/slack_tests.py - 1
Additional Suggestions - 3
  • superset/reports/notifications/slackv2.py - 1
    • Missing test coverage for xlsx · Line 74-75
      The xlsx handling is correctly implemented, but there's no dedicated unit test for `SlackV2Notification._get_inline_files()` with xlsx content. The existing `test_get_inline_files_with_xlsx` tests `SlackNotification` (slack.py), not `SlackV2Notification`. Add a test covering this code path to prevent regressions per rule [11730].
  • superset/reports/notifications/email.py - 1
    • Missing test for CSV path · Line 208-214
      The XLSX attachment path (added in this change) has test coverage via `test_email_content_with_xlsx_attachment`, but the corresponding CSV attachment path lacks a dedicated test. Consider adding a test that validates `NotificationContent(csv=b'csv_content', ...)` produces the correct `email_content.data` attachment.
  • superset-frontend/src/features/alerts/AlertReportModal.tsx - 1
    • Consistency: mixed case in format arrays · Line 2471-2471
      The `['PDF', 'TEXT', 'CSV', 'XLSX']` array at line 2471 uses uppercase values while the `['pdf', 'png', 'csv', 'xlsx']` array at line 2497 uses lowercase keys. This inconsistency increases cognitive load and creates divergence risk if code is copy-pasted between contexts. Consider standardizing on lowercase keys (matching the `FORMAT_OPTIONS_KEY` type) unless uppercase is semantically required.
Review Details
  • Files reviewed - 17 · Commit Range: 9082196..9082196
    • docs/admin_docs/configuration/alerts-reports.mdx
    • superset-frontend/src/features/alerts/AlertReportModal.test.tsx
    • superset-frontend/src/features/alerts/AlertReportModal.tsx
    • superset-frontend/src/features/reports/ReportModal/index.tsx
    • superset-frontend/src/features/reports/types.ts
    • superset/commands/report/exceptions.py
    • superset/commands/report/execute.py
    • superset/reports/models.py
    • superset/reports/notifications/base.py
    • superset/reports/notifications/email.py
    • superset/reports/notifications/slack.py
    • superset/reports/notifications/slackv2.py
    • tests/integration_tests/reports/commands_tests.py
    • tests/integration_tests/reports/utils.py
    • tests/unit_tests/commands/report/execute_test.py
    • tests/unit_tests/reports/notifications/email_tests.py
    • tests/unit_tests/reports/notifications/slack_tests.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment thread docs/admin_docs/configuration/alerts-reports.mdx
Comment thread tests/unit_tests/reports/notifications/slack_tests.py

@rusackas rusackas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of bot review comments. Can we start by assessing/addressing those, and resolving the merge conflict(s)?

@gr33nak gr33nak force-pushed the feat/xlsx-report-attachments branch from 9082196 to f1c5ee7 Compare June 25, 2026 17:37
Comment thread superset/commands/report/exceptions.py
Comment thread superset/commands/report/exceptions.py
@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.54545% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.43%. Comparing base (83f7dc9) to head (3bd6f2b).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
superset/commands/report/execute.py 77.27% 3 Missing and 2 partials ⚠️
superset/reports/notifications/slackv2.py 0.00% 1 Missing and 1 partial ⚠️
superset/reports/notifications/webhook.py 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #41424      +/-   ##
==========================================
- Coverage   64.47%   64.43%   -0.04%     
==========================================
  Files        2661     2662       +1     
  Lines      145673   145854     +181     
  Branches    33620    33652      +32     
==========================================
+ Hits        93916    93988      +72     
- Misses      50053    50160     +107     
- Partials     1704     1706       +2     
Flag Coverage Δ
hive 39.22% <26.19%> (-0.02%) ⬇️
javascript 68.83% <100.00%> (ø)
mysql 57.94% <78.57%> (-0.05%) ⬇️
postgres 58.01% <78.57%> (-0.05%) ⬇️
presto 40.80% <26.19%> (-0.02%) ⬇️
python 59.44% <78.57%> (-0.06%) ⬇️
sqlite 57.67% <78.57%> (-0.05%) ⬇️
unit 100.00% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Adds "Send as Excel" as an attachment format for chart-based alerts and
reports, alongside the existing CSV/PNG/PDF/text options. XLSX bytes are
fetched from the chart data export endpoint (result_format=XLSX), reusing
the same post-processing, index handling, and formula-injection-safe
df_to_excel path as a chart's manual Excel export.

Finalizes the long-stalled PR apache#19810. Resolves the spurious-index-column
issue that blocked the original by fetching pre-rendered XLSX from the
export endpoint instead of re-parsing CSV into a DataFrame locally.

- models: add ReportDataFormat.XLSX + tabular() helper
- execute: unify CSV/XLSX fetching into _get_data(result_format); surface
  format-specific errors (XLSX vs CSV) from the query-context fallback
- notifications: add NotificationContent.xlsx; attach in email, Slack
  (v1/v2), and webhook
- frontend: add "Send as Excel" option to AlertReportModal and ReportModal
- docs: list Excel among report attachment formats
- tests: unit + integration coverage for the XLSX path across all
  notification types

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gr33nak gr33nak force-pushed the feat/xlsx-report-attachments branch from f1c5ee7 to 3bd6f2b Compare June 25, 2026 18:20
@gr33nak

gr33nak commented Jun 25, 2026

Copy link
Copy Markdown
Author

Thanks for taking a look! Just pushed an update: rebased onto master (conflicts resolved), and worked through the bot comments — the two real ones (webhook xlsx attachment + XLSX error misclassification) are fixed with tests; the rest were typing/docstring nits, now addressed.

@bito-code-review bito-code-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Agent Run #daaf18

Actionable Suggestions - 2
  • superset/commands/report/execute.py - 1
  • tests/unit_tests/commands/report/execute_test.py - 1
Additional Suggestions - 1
  • superset/commands/report/execute.py - 1
    • Hardcoded CSV config for XLSX timeout · Line 557-557
      The refactored `_get_data` (line 516) handles both CSV and XLSX but still reads only `ALERT_REPORTS_CSV_REQUEST_TIMEOUT` (line 557). If XLSX generation proves to be significantly slower than CSV in production, operators cannot adjust its timeout independently. The hardcoded CSV name in the config key is also a maintenance risk if the set of tabular formats grows.
Review Details
  • Files reviewed - 19 · Commit Range: 3bd6f2b..3bd6f2b
    • docs/admin_docs/configuration/alerts-reports.mdx
    • superset-frontend/src/features/alerts/AlertReportModal.test.tsx
    • superset-frontend/src/features/alerts/AlertReportModal.tsx
    • superset-frontend/src/features/reports/ReportModal/index.tsx
    • superset-frontend/src/features/reports/types.ts
    • superset/commands/report/exceptions.py
    • superset/commands/report/execute.py
    • superset/reports/models.py
    • superset/reports/notifications/base.py
    • superset/reports/notifications/email.py
    • superset/reports/notifications/slack.py
    • superset/reports/notifications/slackv2.py
    • superset/reports/notifications/webhook.py
    • tests/integration_tests/reports/commands_tests.py
    • tests/integration_tests/reports/utils.py
    • tests/unit_tests/commands/report/execute_test.py
    • tests/unit_tests/reports/notifications/email_tests.py
    • tests/unit_tests/reports/notifications/slack_tests.py
    • tests/unit_tests/reports/notifications/webhook_tests.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

Comment on lines 516 to +588
@@ -522,17 +548,18 @@ def _get_csv_data(self) -> bytes:

if self._report_schedule.chart.query_context is None:
logger.warning("No query context found, taking a screenshot to generate it")
self._update_query_context()
self._update_query_context(failed_error)

try:
csv_data = get_chart_csv_data(
data = get_chart_csv_data(
chart_url=url,
auth_cookies=auth_cookies,
timeout=app.config["ALERT_REPORTS_CSV_REQUEST_TIMEOUT"],
)
elapsed_seconds = (datetime.utcnow() - start_time).total_seconds()
logger.info(
"CSV data generation from %s as user %s took %.2fs - execution_id: %s",
"%s data generation from %s as user %s took %.2fs - execution_id: %s",
label,
url,
username,
elapsed_seconds,
@@ -541,24 +568,24 @@ def _get_csv_data(self) -> bytes:
except SoftTimeLimitExceeded as ex:
elapsed_seconds = (datetime.utcnow() - start_time).total_seconds()
logger.warning(
"CSV generation timeout after %.2fs - execution_id: %s",
"%s generation timeout after %.2fs - execution_id: %s",
label,
elapsed_seconds,
self._execution_id,
)
raise ReportScheduleCsvTimeout() from ex
raise timeout_error() from ex
except Exception as ex:
elapsed_seconds = (datetime.utcnow() - start_time).total_seconds()
logger.exception(
"CSV generation failed after %.2fs - execution_id: %s",
"%s generation failed after %.2fs - execution_id: %s",
label,
elapsed_seconds,
self._execution_id,
)
raise ReportScheduleCsvFailedError(
f"Failed generating csv {str(ex)}"
) from ex
if not csv_data:
raise ReportScheduleCsvFailedError()
return csv_data
raise failed_error(f"Failed generating {label.lower()} {str(ex)}") from ex
if not data:
raise failed_error()
return data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing XLSX timeout test coverage

The _get_data method now dispatches XLSX-specific exceptions (lines 527–532, 571–576, 585), but no test exercises the SoftTimeLimitExceededReportScheduleXlsxTimeout path for XLSX format. The existing tests only cover the CSV timeout variant (test_get_notification_content_csv_format at test line ~1300). Without this test, an incorrect exception mapping for XLSX would go undetected.

Code Review Run #daaf18


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

) -> None:
"""_update_query_context surfaces the caller's error class (XLSX, not CSV)."""
schedule = mocker.Mock(spec=ReportSchedule)
state = BaseReportState(schedule, datetime.utcnow(), uuid4())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deprecated datetime.utcnow usage

datetime.utcnow() is deprecated in Python 3.12+ and returns a naive datetime. Replace with datetime.now(timezone.utc) from datetime module.

Code Review Run #daaf18


Should Bito avoid suggestions like this for future reviews? (Manage Rules)

  • Yes, avoid them

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

alert-reports Namespace | Anything related to the Alert & Reports feature change:backend Requires changing the backend change:frontend Requires changing the frontend doc Namespace | Anything related to documentation size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants