Skip to content

fix(reports): escape LIKE wildcards in text filter and preserve typed screenshot width#40980

Open
sadpandajoe wants to merge 1 commit into
masterfrom
fix-alert-report-command-skips
Open

fix(reports): escape LIKE wildcards in text filter and preserve typed screenshot width#40980
sadpandajoe wants to merge 1 commit into
masterfrom
fix-alert-report-command-skips

Conversation

@sadpandajoe

Copy link
Copy Markdown
Member

SUMMARY

Two small correctness fixes in the Alerts & Reports feature, plus regression tests.

1. Escape LIKE wildcards in the reports text filter. The text search filter on
the alerts/reports list passed user input directly into ILIKE, so % and _ were
interpreted as wildcards rather than literal characters. A search for a_b would
match axb, etc. The fix adds a local _escape_like() helper (mirroring the existing
daos/datasource.py precedent) and passes escape="\" to each filter clause so the
characters match literally.

2. Preserve a typed 0 in the ReportModal custom screenshot-width input. The input
used || null for the submitted value and || '' for the display value, which silently
coerced a typed 0 into "no custom width". The fix uses ?? '' for display and an
explicit Number.isNaN() check on change. A typed 0 is now submitted and surfaced by
the server's min-width validation instead of being dropped on the client; clearing the
field still yields an empty value (parsed NaN → null).

Regression tests. Adds backend unit tests pinning the report DAO's autoescape
behavior and the update command's extra.dashboard.activeTabs validation (both already
correct in code), and frontend RTL tests for the width-input behavior above.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — behavior fixes, no visual change to the layout.

TESTING INSTRUCTIONS

Backend:

pytest tests/unit_tests/reports/filters_test.py \
       tests/unit_tests/reports/dao_test.py \
       tests/unit_tests/commands/report/update_test.py

Frontend:

cd superset-frontend
npm run test -- src/features/reports/ReportModal/ReportModal.test.tsx

Manual: in the Alerts & Reports list, search the name filter for a term containing %
or _ and confirm it matches literally. In the report/alert modal for a non-dashboard
chart, type 0 into the custom screenshot width and confirm the value is preserved
(and rejected by validation) rather than silently cleared.

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

@netlify

netlify Bot commented Jun 11, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 7d6a421
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a2b012bd3f53c000864d25e
😎 Deploy Preview https://deploy-preview-40980--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.

Comment thread superset/reports/filters.py Outdated
@bito-code-review

Copy link
Copy Markdown
Contributor

The reported issue is correct. The _escape_like function expects a string, but ReportScheduleAllTextFilter.apply passes the raw value from the filter, which can be a non-string type (e.g., an integer). Calling .replace() on a non-string type raises an AttributeError.

To resolve this, you should coerce the value to a string before passing it to _escape_like in superset/reports/filters.py.

superset/reports/filters.py

if not value:
            return query
        ilike_value = f"%{_escape_like(str(value))}%"
        return query.filter(
            or_(

@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 64.19%. Comparing base (3380496) to head (2a161db).
⚠️ Report is 29 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master   #40980   +/-   ##
=======================================
  Coverage   64.19%   64.19%           
=======================================
  Files        2655     2655           
  Lines      143914   143960   +46     
  Branches    33178    33189   +11     
=======================================
+ Hits        92379    92409   +30     
- Misses      49916    49927   +11     
- Partials     1619     1624    +5     
Flag Coverage Δ
hive 39.44% <33.33%> (-0.02%) ⬇️
javascript 68.04% <100.00%> (+<0.01%) ⬆️
mysql 58.19% <100.00%> (+<0.01%) ⬆️
postgres 58.26% <100.00%> (+<0.01%) ⬆️
presto 41.03% <33.33%> (-0.02%) ⬇️
python 59.73% <100.00%> (+<0.01%) ⬆️
sqlite 57.88% <100.00%> (+<0.01%) ⬆️
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.

@dosubot dosubot Bot added the alert-reports Namespace | Anything related to the Alert & Reports feature label Jun 11, 2026
@sadpandajoe sadpandajoe requested a review from Copilot June 11, 2026 19:00

Copilot AI 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.

Pull request overview

This PR makes two correctness fixes in the Alerts & Reports feature: (1) it ensures user-entered %, _, and \ are treated as literals (not wildcards) in the reports “All Text” filter, and (2) it preserves a user-typed 0 in the custom screenshot-width input so server-side validation can handle it. It also adds regression tests to lock in the intended backend/command validation and frontend input behavior.

Changes:

  • Escape LIKE/ILIKE wildcard characters in the reports list text filter and use an explicit ESCAPE '\' clause for literal matching.
  • Fix ReportModal screenshot width input handling to avoid coercing 0 to “empty” by using ?? and Number.isNaN logic.
  • Add backend unit tests for wildcard escaping / extra_json autoescape and update validation, plus a frontend RTL test for the 0 width behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
superset/reports/filters.py Escapes wildcard characters for ILIKE-based “All Text” filtering and adds escape="\" to match literals.
tests/unit_tests/reports/filters_test.py Adds a regression test asserting wildcard/backslash escaping is applied to each ILIKE clause.
tests/unit_tests/reports/dao_test.py Adds tests pinning extra_json.contains(..., autoescape=True) behavior for strings containing %/_.
tests/unit_tests/commands/report/update_test.py Adds update validation tests for extra.dashboard.activeTabs against the model dashboard layout.
superset-frontend/src/features/reports/ReportModal/index.tsx Preserves typed 0 for custom_width by switching `
superset-frontend/src/features/reports/ReportModal/ReportModal.test.tsx Adds RTL test ensuring typed 0 is displayed/preserved and clearing results in an empty field.

… screenshot width

The report/alert list text filter passed user input straight into ILIKE,
so % and _ were treated as wildcards. Add a local _escape_like() helper
(mirroring the daos/datasource.py precedent) and pass escape="\\" to each
filter clause so the characters match literally.

The ReportModal custom screenshot-width input used `|| null` / `|| ''`,
which silently coerced a typed 0 to "no custom width". Use `?? ''` for
display and an explicit Number.isNaN() check on change, so a typed 0 is
submitted and surfaced by the server's min-width validation instead of
being dropped on the client.

Add regression tests covering report DAO autoescape behavior and the
update command's extra.dashboard.activeTabs validation to lock in the
existing correct behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sadpandajoe sadpandajoe force-pushed the fix-alert-report-command-skips branch from 7d6a421 to 2a161db Compare June 11, 2026 19:04

@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 #27ed6d

Actionable Suggestions - 2
  • tests/unit_tests/reports/filters_test.py - 1
  • tests/unit_tests/reports/dao_test.py - 1
    • SEMANTIC_DUPLICATION: Duplicate test coverage · Line 94-111
Additional Suggestions - 1
  • tests/unit_tests/reports/dao_test.py - 1
    • Inconsistent test pattern: mocks vs integration · Line 114-130
      This test uses mocks instead of the integration test pattern (real database session) established in `tests/unit_tests/daos/test_report_dao.py`. Converting to an integration test would verify actual database behavior for LIKE wildcard escaping.
Review Details
  • Files reviewed - 6 · Commit Range: 7d6a421..7d6a421
    • superset-frontend/src/features/reports/ReportModal/ReportModal.test.tsx
    • superset-frontend/src/features/reports/ReportModal/index.tsx
    • superset/reports/filters.py
    • tests/unit_tests/commands/report/update_test.py
    • tests/unit_tests/reports/dao_test.py
    • tests/unit_tests/reports/filters_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

f.apply(query, "50%_off\\promo")

# %, _ and \ are all escaped, and the literal is wrapped for a "contains" match
expected = "%50\\%\\_off\\\\promo%"

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.

Test expectation incorrect

The _escape_like function (filters.py:33) doubles each backslash once via value.replace("\\", "\\\\"), so a single \ in input becomes \\ in output. The test expectation has \\\\ (four backslashes) before promo but the implementation produces \\ (two backslashes). This test will fail at runtime.

Code suggestion
Check the AI-generated fix before applying
 --- a/tests/unit_tests/reports/filters_test.py
 +++ b/tests/unit_tests/reports/filters_test.py
 @@ -81 +81 @@
 -    expected = "%50\\%\\_off\\\\promo%"
 +    expected = "%50\\%\\_off\\promo%"

Code Review Run #27ed6d


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

  • Yes, avoid them

Comment on lines +94 to +111
@patch("superset.daos.report.ReportSchedule")
@patch("superset.daos.report.db")
def test_find_by_extra_metadata_escapes_like_wildcards(
mock_db: MagicMock, mock_report_schedule: MagicMock
) -> None:
"""LIKE wildcards in the slug must be escaped so they match literally."""
from superset.daos.report import ReportScheduleDAO

expected: list[MagicMock] = [MagicMock()]
mock_db.session.query.return_value.filter.return_value.all.return_value = expected

result = ReportScheduleDAO.find_by_extra_metadata("100%_off")

assert result is expected
# autoescape=True is what neutralises the LIKE wildcards in the slug
mock_report_schedule.extra_json.contains.assert_called_once_with(
"100%_off", autoescape=True
)

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.

SEMANTIC_DUPLICATION: Duplicate test coverage

This test duplicates existing integration tests in tests/unit_tests/daos/test_report_dao.py (lines 71-92) that verify the same LIKE wildcard escaping behavior. Integration tests with real database sessions are superior because they verify actual query behavior, not just mock call arguments.

Code Review Run #27ed6d


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

  • Yes, avoid them

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

Actionable Suggestions - 1
  • superset/reports/filters.py - 1
Filtered by Review Rules

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

  • tests/unit_tests/reports/dao_test.py - 2
Review Details
  • Files reviewed - 6 · Commit Range: 2a161db..2a161db
    • superset-frontend/src/features/reports/ReportModal/ReportModal.test.tsx
    • superset-frontend/src/features/reports/ReportModal/index.tsx
    • superset/reports/filters.py
    • tests/unit_tests/commands/report/update_test.py
    • tests/unit_tests/reports/dao_test.py
    • tests/unit_tests/reports/filters_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 on lines +28 to +33
def _escape_like(value: str) -> str:
"""
Escape LIKE/ILIKE wildcard characters so user-supplied search text is matched
literally instead of being interpreted as wildcards (e.g. ``%`` and ``_``).
"""
return value.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")

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.

Duplicate function across modules

The _escape_like function is duplicated identically in superset/daos/base.py:84. Maintaining two identical copies creates maintenance risk: future changes must be applied in both locations or the codebase will become inconsistent.

Code Review Run #d65847


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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants