Skip to content

fix(reports): export server-paginated table row limits#41103

Open
innovark37 wants to merge 5 commits into
apache:masterfrom
innovark37:fix/export-server-paginated
Open

fix(reports): export server-paginated table row limits#41103
innovark37 wants to merge 5 commits into
apache:masterfrom
innovark37:fix/export-server-paginated

Conversation

@innovark37

Copy link
Copy Markdown
Contributor

SUMMARY

Scheduled report CSV/XLSX exports for Table charts with server-side pagination used the saved chart query context from the preview path. That query context contains the current page data query, where row_limit is based on Server Page Length, not the configured Row limit.

This made report exports behave differently from frontend exports:

  • Frontend export uses Row limit and starts from row_offset = 0.
  • Report worker export used Server Page Length and could export only one server-paginated page.
  • The saved server pagination query context also includes an is_rowcount query, which is useful for the table preview but unnecessary for file export.

This change makes the report worker send chart data requests using the same payload shape as frontend downloads. For server-paginated table exports, the worker prepares the saved query context for export by:

  • setting the data query row_limit from form_data.row_limit;
  • resetting row_offset to 0;
  • removing the is_rowcount query;
  • posting to the chart data endpoint with result_format set to CSV/XLSX and result_type set to post-processed.

Alternative considered: normalize this on the chart data backend instead of in the worker. For example:

def _normalize_server_paginated_export(
    query_context: dict[str, Any],
) -> None:
    """Make a saved server-paginated table query match a UI download query."""
    if query_context.get("result_format") not in {
        result_format.value for result_format in ChartDataResultFormat.table_like()
    }:
        return

    form_data = query_context.get("form_data") or {}
    queries = query_context.get("queries") or []
    if not form_data.get("server_pagination") or not queries:
        return

    queries[0]["row_limit"] = form_data.get("row_limit") or 0
    queries[0]["row_offset"] = 0
    query_context["queries"] = [
        query for query in queries if not query.get("is_rowcount")
    ]

That would make the rule closer to the chart data execution layer, but it would also affect all POST callers of the chart data API. The worker-level change keeps the behavior scoped to scheduled report exports and avoids changing the public chart data API contract.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

Not applicable. This is a backend report export fix.

TESTING INSTRUCTIONS

  1. Create a Table chart with server-side pagination enabled.
  2. Set Server Page Length lower than Row limit.
  3. Create a scheduled report export for CSV or XLSX.
  4. Verify the exported file uses the configured Row limit, not Server Page Length.
  5. Verify the export does not issue/use the row count query.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags: ALERT_REPORTS
  • 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

@dosubot dosubot Bot added alert-reports Namespace | Anything related to the Alert & Reports feature change:backend Requires changing the backend labels Jun 16, 2026
Comment thread superset/commands/report/execute.py Outdated
@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. The newly introduced methods _get_chart_data_request_payload and _post_chart_data in superset/commands/report/execute.py lack docstrings, which is required by the project's coding standards.

To resolve this, you should add docstrings to both methods. Here is the suggested implementation:

    def _get_chart_data_request_payload(
        self,
        result_format: ChartDataResultFormat,
    ) -> dict[str, Any]:
        """
        Build the POST payload for chart data export.

        :param result_format: The desired format for the chart data.
        :return: A dictionary representing the request payload.
        :raises ReportScheduleExecuteUnexpectedError: If the query context is invalid.
        """
        # ... existing implementation ...

    @staticmethod
    def _post_chart_data(
        chart_url: str,
        auth_cookies: Optional[dict[str, str]],
        request_payload: dict[str, Any],
    ) -> Optional[bytes]:
        """
        POST the chart data request to the provided URL.

        :param chart_url: The URL to send the POST request to.
        :param auth_cookies: Authentication cookies for the request.
        :param request_payload: The payload to send.
        :return: The response content as bytes, or None if no cookies provided.
        :raises URLError: If the request fails.
        """
        # ... existing implementation ...

I have checked the PR comments, and there are no other actionable suggestions or comments to address at this time. Would you like me to proceed with any other tasks?

superset/commands/report/execute.py

def _get_chart_data_request_payload(
        self,
        result_format: ChartDataResultFormat,
    ) -> dict[str, Any]:
        """
        Build the POST payload for chart data export.

        :param result_format: The desired format for the chart data.
        :return: A dictionary representing the request payload.
        :raises ReportScheduleExecuteUnexpectedError: If the query context is invalid.
        """

    @staticmethod
    def _post_chart_data(
        chart_url: str,
        auth_cookies: Optional[dict[str, str]],
        request_payload: dict[str, Any],
    ) -> Optional[bytes]:
        """
        POST the chart data request to the provided URL.

        :param chart_url: The URL to send the POST request to.
        :param auth_cookies: Authentication cookies for the request.
        :param request_payload: The payload to send.
        :return: The response content as bytes, or None if no cookies provided.
        :raises URLError: If the request fails.
        """

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 14.54545% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.19%. Comparing base (7b4efac) to head (7fc151f).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
superset/commands/report/execute.py 14.54% 47 Missing ⚠️

❗ There is a different number of reports uploaded between BASE (7b4efac) and HEAD (7fc151f). Click for more details.

HEAD has 75 uploads less than BASE
Flag BASE (7b4efac) HEAD (7fc151f)
python 54 3
presto 9 1
hive 9 1
unit 9 1
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #41103      +/-   ##
==========================================
- Coverage   64.32%   56.19%   -8.13%     
==========================================
  Files        2652     2652              
  Lines      144728   144780      +52     
  Branches    33384    33394      +10     
==========================================
- Hits        93090    81357   -11733     
- Misses      49985    62697   +12712     
+ Partials     1653      726     -927     
Flag Coverage Δ
hive 39.32% <14.54%> (-0.02%) ⬇️
mysql ?
postgres ?
presto 40.89% <14.54%> (-0.03%) ⬇️
python 42.19% <14.54%> (-17.38%) ⬇️
sqlite ?
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.

Comment thread superset/commands/report/execute.py Outdated

@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 #dc034c

Actionable Suggestions - 2
  • superset/commands/report/execute.py - 2
Additional Suggestions - 1
  • superset/commands/report/execute.py - 1
    • Missing docstring and timeout · Line 565-590
      The `_post_chart_data()` method lacks a docstring. Per BITO.md rule 12147, all new Python functions must include inline documentation. Additionally, the HTTP request has no timeout, which could cause report generation to hang indefinitely if the API is unresponsive.
Filtered by Review Rules

Bito filtered these suggestions based on rules created automatically for your feedback. Manage rules.

  • superset/commands/report/execute.py - 1
Review Details
  • Files reviewed - 2 · Commit Range: d9fc509..d9fc509
    • superset/commands/report/execute.py
    • tests/unit_tests/commands/report/execute_test.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ 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 superset/commands/report/execute.py
Comment thread superset/commands/report/execute.py Outdated
@bito-code-review

bito-code-review Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #5bb637

Actionable Suggestions - 0
Additional Suggestions - 1
  • superset/commands/report/execute.py - 1
    • Redundant contextlib.closing() wrapper · Line 21-21
      The `from contextlib import closing` import and wrapper around `urllib.request.build_opener().open()` at line 605-607 is unnecessary. `HTTPResponse` in Python 3 natively supports the `with` statement via `__enter__`/`__exit__` methods, so `closing()` adds no functional value and introduces visual noise. This pattern also diverges from similar HTTP request handling in the codebase (see `scripts/change_detector.py:66`, `superset/tasks/utils.py:133`).
      Code suggestion
      --- a/superset/commands/report/execute.py
      +++ b/superset/commands/report/execute.py
       @@ -18,7 +18,6 @@
        import urllib.parse
        import urllib.request
        from collections.abc import Sequence
      -from contextlib import closing
        from datetime import datetime, timedelta
        from typing import Any, Optional, Union
        from urllib.error import URLError
       @@ -602,9 +601,8 @@
                    },
                    method="POST",
                )
      -        with closing(
      -            urllib.request.build_opener().open(request)  # noqa: S310
      -        ) as response:
      +        with urllib.request.build_opener().open(
      +            request  # noqa: S310
      +        ) as response:
                    content = response.read()
                    if response.getcode() != 200:
                        raise URLError(response.getcode())
Review Details
  • Files reviewed - 2 · Commit Range: d9fc509..7fc151f
    • superset/commands/report/execute.py
    • tests/unit_tests/commands/report/execute_test.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ 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

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 size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant