test(histogram): metric filters require aggregation in buildQuery (#30330)#40617
Conversation
…0330) Regression for #30330: the histogram buildQuery unconditionally sets metrics: undefined. When a user adds a HAVING-clause "filter by metric", the generated SQL has a HAVING clause but no aggregated metric or GROUP BY — invalid SQL that most databases (including Druid) reject. This test asserts the desired behaviour: when HAVING adhoc_filters are present the query must include at least one metric so the HAVING clause has an aggregated value to filter on. Fix target: superset-frontend/plugins/plugin-chart-echarts/src/Histogram/buildQuery.ts — detect HAVING filters and preserve (or synthesise) metrics accordingly. Closes #30330 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review Agent Run #384085Actionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
… present Closes #30330 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
✅ Deploy Preview for superset-docs-preview ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #40617 +/- ##
=======================================
Coverage 63.94% 63.95%
=======================================
Files 2658 2658
Lines 143011 143015 +4
Branches 32866 32869 +3
=======================================
+ Hits 91453 91461 +8
+ Misses 49995 49991 -4
Partials 1563 1563
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code Review Agent Run #89cdb2Actionable Suggestions - 0Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
aminghadersohi
left a comment
There was a problem hiding this comment.
CI all-green. Full scan ran — findings below as inline comments. Second opinion not needed (2 TypeScript files, ~95 lines, no security/migrations/multi-tenant code).
| const { column, groupby = [] } = formData; | ||
| const { column, groupby = [], adhoc_filters } = formData; | ||
| const hasHavingFilter = (adhoc_filters ?? []).some( | ||
| (filter: { clause?: string }) => filter.clause === 'HAVING', |
There was a problem hiding this comment.
**MEDIUM — inline type should use **
HistogramFormData extends QueryFormData which already types adhoc_filters?: AdhocFilter[] | null via BaseFormData. TypeScript can infer filter: AdhocFilter in the .some() callback without an explicit annotation. The inline annotation introduces two problems:
- Makes
clauseoptional (adds?) whenBaseAdhocFilter.clauseis the required union'WHERE' | 'HAVING'. - Widens the element type from the literal union to
string, losing narrowing.
Fix — remove the annotation and let TypeScript infer, or use the proper type:
(filter: AdhocFilter) => filter.clause === 'HAVING',AdhocFilter is already re-exported from @superset-ui/core — no new import needed.
| export default function buildQuery(formData: HistogramFormData) { | ||
| const { column, groupby = [] } = formData; | ||
| const { column, groupby = [], adhoc_filters } = formData; | ||
| const hasHavingFilter = (adhoc_filters ?? []).some( |
There was a problem hiding this comment.
MEDIUM — extra_form_data.adhoc_filters HAVING filters not detected
adhoc_filters here is formData.adhoc_filters only. HAVING filters injected by dashboard native filter boxes arrive via extra_form_data.adhoc_filters, which buildQueryContext merges into the built query — but those filters are invisible to this check. If a dashboard box injects a HAVING clause, hasHavingFilter stays false, metrics stays undefined, and the invalid-SQL path from #30330 fires.
This is a niche path (HAVING filters via dashboard boxes are uncommon), so not a hard blocker. Worth a follow-up issue or an inline comment scoping the fix to the direct-filter path.
|
|
||
| export default function buildQuery(formData: HistogramFormData) { | ||
| const { column, groupby = [] } = formData; | ||
| const { column, groupby = [], adhoc_filters } = formData; |
There was a problem hiding this comment.
MEDIUM — PR title and description say test-only but the diff includes production code
The PR title uses the Conventional Commits test type (= tests only) and the description opens with "This is a test-only PR", but this file has +13/−2 lines of production logic — the actual fix. The description does go on to describe the fix approach, so intent is clear, but the title will mislead the Conventional Commits changelog tooling and reviewers scanning git log.
Suggested title: fix(histogram): synthesise COUNT(*) metric when HAVING filter is present (#30330)
Not a merge blocker — the code is correct — but worth fixing before merge.
| const { column, groupby = [] } = formData; | ||
| const { column, groupby = [], adhoc_filters } = formData; | ||
| const hasHavingFilter = (adhoc_filters ?? []).some( | ||
| (filter: { clause?: string }) => filter.clause === 'HAVING', |
There was a problem hiding this comment.
MEDIUM — inline type { clause?: string } should use AdhocFilter
HistogramFormData extends QueryFormData which already types adhoc_filters?: AdhocFilter[] | null via BaseFormData. TypeScript can infer filter: AdhocFilter in the .some() callback without an explicit annotation. The inline annotation introduces two problems:
- Makes
clauseoptional (adds?) whenBaseAdhocFilter.clauseis the required union'WHERE' | 'HAVING'. - Widens the element type from the literal union to
string, losing narrowing.
Fix — remove the annotation and let TypeScript infer, or use the proper type:
(filter: AdhocFilter) => filter.clause === 'HAVING',AdhocFilter is already re-exported from @superset-ui/core — no new import needed.
| export default function buildQuery(formData: HistogramFormData) { | ||
| const { column, groupby = [] } = formData; | ||
| const { column, groupby = [], adhoc_filters } = formData; | ||
| const hasHavingFilter = (adhoc_filters ?? []).some( |
There was a problem hiding this comment.
MEDIUM — extra_form_data.adhoc_filters HAVING filters not detected
adhoc_filters here is formData.adhoc_filters only. HAVING filters injected by dashboard native filter boxes arrive via extra_form_data.adhoc_filters, which buildQueryContext merges into the built query — but those filters are invisible to this check. If a dashboard box injects a HAVING clause, hasHavingFilter stays false, metrics stays undefined, and the invalid-SQL path from #30330 fires.
This is a niche path (HAVING filters via dashboard boxes are uncommon), so not a hard blocker. Worth a follow-up issue or an inline comment scoping the fix to the direct-filter path.
|
|
||
| export default function buildQuery(formData: HistogramFormData) { | ||
| const { column, groupby = [] } = formData; | ||
| const { column, groupby = [], adhoc_filters } = formData; |
There was a problem hiding this comment.
MEDIUM — PR title and description say "test-only" but the diff includes production code
The PR title uses the Conventional Commits test type (= tests only) and the description opens with "This is a test-only PR", but this file has +13/−2 lines of production logic — the actual fix. The description does go on to describe the fix approach, so intent is clear, but the title will mislead changelog tooling and reviewers scanning git log.
Suggested title: fix(histogram): synthesise COUNT(*) metric when HAVING filter is present (#30330)
Not a merge blocker — the code is correct — but worth fixing before merge.
| // HAVING filters without aggregation produce invalid SQL. | ||
| // The query must include at least one metric when HAVING filters are present. | ||
| expect(query.metrics).toBeDefined(); | ||
| expect((query.metrics as unknown[]).length).toBeGreaterThan(0); |
There was a problem hiding this comment.
NIT — as unknown[] cast loses type safety; prefer .toHaveLength()
// before
expect((query.metrics as unknown[]).length).toBeGreaterThan(0);
// after
expect(query.metrics).toHaveLength(1);toHaveLength works directly on arrays, produces a better failure message, and avoids the unsafe cast. If you also want to lock down the synthesised metric shape:
expect(query.metrics?.[0]).toMatchObject({ expressionType: 'SQL', label: 'COUNT(*)' });| expect(query.columns).toEqual(['category', 'price']); | ||
| }); | ||
|
|
||
| test('Regression for #30330: HAVING-clause metric filters require aggregation in the query', () => { |
There was a problem hiding this comment.
NIT — test name doesn't follow the 'should ...' convention used by the other two tests
Tests 1 and 2 use 'should ...' names. Consider:
test('should include COUNT(*) metric when HAVING filter is present', () => {Keep the #30330 reference and the bug explanation in the block comment, where they add context without cluttering the Jest output.
| const queryContext = buildQuery({ ...baseFormData, groupby: ['category'] }); | ||
| const [query] = queryContext.queries; | ||
| expect(query.columns).toEqual(['category', 'price']); | ||
| }); |
There was a problem hiding this comment.
NIT — missing complementary WHERE-only filter test
There's no test confirming that a WHERE-only filter leaves metrics as undefined. This is the direct complement to the regression test and guards against a future refactor accidentally triggering on WHERE filters:
test('should not inject metrics for WHERE-only filters', () => {
const formData = {
...baseFormData,
adhoc_filters: [
{ clause: 'WHERE', expressionType: 'SQL', sqlExpression: 'price > 10' },
],
};
const [query] = buildQuery(formData as HistogramFormData).queries;
expect(query.metrics).toBeUndefined();
});
SUMMARY
This is a test-only PR opened as a TDD-style validation of issue #30330.
#30330 reports that the Histogram chart fails with a database error when a "filter by metric" (HAVING clause) is added. rusackas confirmed the bug is still live on master.
Root cause:
buildQuery.tsunconditionally setsmetrics: undefined. Any HAVING-clauseadhoc_filtertherefore produces SQL with aHAVINGclause but no aggregated metric orGROUP BY— invalid SQL that Druid (and most databases) reject.This PR adds a
buildQuerytest with three cases:should build query with column and no metrics— baseline: histogram sends no metrics (passes today).should include groupby columns in query columns— groupby passthrough (passes today).Regression for #30330— asserts that when a HAVINGadhoc_filteris present,query.metricsis defined and non-empty. Fails today (metrics is alwaysundefined).How to interpret CI
buildQuery.ts— detect HAVINGadhoc_filtersand preserve or synthesise aCOUNT(*)metric so the HAVING clause has an aggregated value to filter on.TESTING INSTRUCTIONS
ADDITIONAL INFORMATION
🤖 Generated with Claude Code