Skip to content

feat: Convert SQL to Builder configs#2667

Closed
pulpdrew wants to merge 2 commits into
mainfrom
drew/sql-builder-convert
Closed

feat: Convert SQL to Builder configs#2667
pulpdrew wants to merge 2 commits into
mainfrom
drew/sql-builder-convert

Conversation

@pulpdrew

Copy link
Copy Markdown
Contributor

Summary

Screenshots or video

Before After

How to test on Vercel preview

Preview routes:

Steps:

References

  • Linear Issue:
  • Related PRs:

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jul 16, 2026 9:20pm
hyperdx-storybook Ready Ready Preview, Comment Jul 16, 2026 9:20pm

Request Review

@changeset-bot

changeset-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6198b4b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@hyperdx/common-utils Minor
@hyperdx/app Minor
@hyperdx/api Minor
@hyperdx/otel-collector Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 242 passed • 1 skipped • 1107s

Status Count
✅ Passed 242
❌ Failed 0
⚠️ Flaky 1
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a SQL → Builder conversion path (convertRawSqlToBuilderConfig) to complement the existing Builder → SQL direction, and replaces useBuilderToSqlConversion with a two-way useBuilderSqlConversion hook. The hook uses a lastEditedModeRef (with a null "in-sync" state) and a request-ID guard to ensure hand-edited queries are never clobbered by a stale regeneration from the other side.

  • rawSqlToBuilder.ts (1 476 lines): parses ClickHouse SQL, strips macro-derived and builder-internal clauses (time-range predicates, $__filters, series-limit OR-group), detects time buckets and aggregation shapes, validates the result against the target display type, and surfaces user-friendly SqlToBuilderError messages for anything the builder cannot represent.
  • useBuilderSqlConversion: extends the original one-way hook with a SQL → Builder effect; the isUserChange flag on ChartFormSetValue plumbs edit-attribution through controls that bypass RHF's native change events, and all components in the form tree are updated to pass this flag consistently.
  • A 1 355-line unit test suite and a new full-stack E2E round-trip test cover the conversion logic.

Confidence Score: 4/5

Safe to merge for the two-way conversion wiring; the converter's silent ROLLUP/CUBE drop (flagged previously) remains unaddressed and can produce a wrong empty GROUP BY without notifying the user.

The new hook logic, edit-attribution plumbing, and isUserChange propagation are all correct. The converter itself is well-tested and handles the common cases soundly. The outstanding concern is the silent ExpressionList filter in parseGroupBy: a hand-written query using GROUP BY ROLLUP(…) converts to groupBy: '' with no error, so the builder silently drops a user-authored grouping modifier.

packages/common-utils/src/core/rawSqlToBuilder.ts — specifically the parseGroupBy ExpressionList filter and parseOrderBy direction interpolation noted in prior review threads.

Important Files Changed

Filename Overview
packages/common-utils/src/core/rawSqlToBuilder.ts New 1 476-line SQL→Builder converter: correctly handles macros, time-range predicates, time-bucket detection, aggregation unwrapping, ratio series, WHERE broadcasting, and display-type validation. One unused isWithElement import; the previously flagged ROLLUP/CUBE silent-drop and el.direction interpolation are not yet addressed.
packages/app/src/components/DBEditTimeChartForm/useBuilderSqlConversion.ts Replaces one-way Builder→SQL hook with a two-way conversion hook. The request-ID guard, lastEditedModeRef null-sync state, and isUserChange flag plumbing all look correct; async result is safely discarded when a newer toggle supersedes it.
packages/app/src/components/ChartEditor/types.ts Adds ChartFormSetValue / ChartFormSetValueOptions types that extend RHF's UseFormSetValue with an isUserChange flag; the generic signature remains assignable to the plain UseFormSetValue so it threads through existing components unchanged.
packages/common-utils/src/types.ts Exports DISPLAY_TYPE_LABELS record and the Limit type. StackedBar and Bar share the label 'Bar' (flagged in a previous thread); otherwise straightforward.
packages/common-utils/src/core/renderChartConfig.ts Extracts isNumericAggFn as an exported helper consumed by rawSqlToBuilder for symmetric unwrapping of toFloat64OrDefault(toString(…)) wrappers; logic is unchanged, just lifted.
packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx Threads setValueWithEditTracking from useBuilderSqlConversion into all controls; user-initiated setValue calls consistently pass { isUserChange: true } while programmatic writes use plain setValue.
packages/common-utils/src/tests/rawSqlToBuilder.test.ts Comprehensive 1 355-line test suite covering source validation, time-range predicate stripping, time-bucket detection, aggregation parsing, WHERE broadcasting, display-type validation, and round-trip fidelity via renderBuilderConfigAsSqlTemplate.
packages/app/src/components/DBEditTimeChartForm/tests/useBuilderSqlConversion.test.ts Unit tests for classifyFormEdit cover all mode/field combinations cleanly.
packages/app/tests/e2e/features/chart-explorer.spec.ts Adds a full-stack E2E round-trip test: builder (count + ServiceName group-by) → SQL → builder → SQL, verifying the converted config survives a full toggle cycle.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant EditTimeChartForm
    participant useBuilderSqlConversion
    participant renderBuilderConfigAsSqlTemplate
    participant convertRawSqlToBuilderConfig

    Note over useBuilderSqlConversion: lastEditedModeRef = 'builder' | 'sql' | null

    User->>EditTimeChartForm: Edit builder field
    EditTimeChartForm->>useBuilderSqlConversion: "setValueWithEditTracking(field, value, {isUserChange:true})"
    useBuilderSqlConversion->>useBuilderSqlConversion: "lastEditedModeRef = 'builder'"

    User->>EditTimeChartForm: Toggle to SQL mode
    EditTimeChartForm->>useBuilderSqlConversion: "configType = 'sql'"
    useBuilderSqlConversion->>useBuilderSqlConversion: "guard lastEditedModeRef === 'builder'"
    useBuilderSqlConversion->>renderBuilderConfigAsSqlTemplate: convertFormState
    renderBuilderConfigAsSqlTemplate-->>useBuilderSqlConversion: sql
    useBuilderSqlConversion->>EditTimeChartForm: setValue('sqlTemplate', sql)
    useBuilderSqlConversion->>useBuilderSqlConversion: "lastEditedModeRef = null"

    User->>EditTimeChartForm: Edit SQL editor
    EditTimeChartForm->>useBuilderSqlConversion: "watch fires type=change name=sqlTemplate"
    useBuilderSqlConversion->>useBuilderSqlConversion: "lastEditedModeRef = 'sql'"

    User->>EditTimeChartForm: Toggle to Builder mode
    EditTimeChartForm->>useBuilderSqlConversion: "configType = 'builder'"
    useBuilderSqlConversion->>useBuilderSqlConversion: "guard lastEditedModeRef === 'sql'"
    useBuilderSqlConversion->>convertRawSqlToBuilderConfig: sqlTemplate + displayType + from
    convertRawSqlToBuilderConfig-->>useBuilderSqlConversion: SqlToBuilderResult
    useBuilderSqlConversion->>EditTimeChartForm: setValue(select/where/groupBy/granularity)
    useBuilderSqlConversion->>useBuilderSqlConversion: "lastEditedModeRef = null"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant EditTimeChartForm
    participant useBuilderSqlConversion
    participant renderBuilderConfigAsSqlTemplate
    participant convertRawSqlToBuilderConfig

    Note over useBuilderSqlConversion: lastEditedModeRef = 'builder' | 'sql' | null

    User->>EditTimeChartForm: Edit builder field
    EditTimeChartForm->>useBuilderSqlConversion: "setValueWithEditTracking(field, value, {isUserChange:true})"
    useBuilderSqlConversion->>useBuilderSqlConversion: "lastEditedModeRef = 'builder'"

    User->>EditTimeChartForm: Toggle to SQL mode
    EditTimeChartForm->>useBuilderSqlConversion: "configType = 'sql'"
    useBuilderSqlConversion->>useBuilderSqlConversion: "guard lastEditedModeRef === 'builder'"
    useBuilderSqlConversion->>renderBuilderConfigAsSqlTemplate: convertFormState
    renderBuilderConfigAsSqlTemplate-->>useBuilderSqlConversion: sql
    useBuilderSqlConversion->>EditTimeChartForm: setValue('sqlTemplate', sql)
    useBuilderSqlConversion->>useBuilderSqlConversion: "lastEditedModeRef = null"

    User->>EditTimeChartForm: Edit SQL editor
    EditTimeChartForm->>useBuilderSqlConversion: "watch fires type=change name=sqlTemplate"
    useBuilderSqlConversion->>useBuilderSqlConversion: "lastEditedModeRef = 'sql'"

    User->>EditTimeChartForm: Toggle to Builder mode
    EditTimeChartForm->>useBuilderSqlConversion: "configType = 'builder'"
    useBuilderSqlConversion->>useBuilderSqlConversion: "guard lastEditedModeRef === 'sql'"
    useBuilderSqlConversion->>convertRawSqlToBuilderConfig: sqlTemplate + displayType + from
    convertRawSqlToBuilderConfig-->>useBuilderSqlConversion: SqlToBuilderResult
    useBuilderSqlConversion->>EditTimeChartForm: setValue(select/where/groupBy/granularity)
    useBuilderSqlConversion->>useBuilderSqlConversion: "lastEditedModeRef = null"
Loading

Reviews (2): Last reviewed commit: "fix: Track changes from unregistered fie..." | Re-trigger Greptile

Comment thread packages/common-utils/src/core/rawSqlToBuilder.ts
Comment thread packages/common-utils/src/core/rawSqlToBuilder.ts
Comment on lines 44 to +56
}

/** User-facing display-type labels */
export const DISPLAY_TYPE_LABELS: Record<DisplayType, string> = {
[DisplayType.Line]: 'Time Series',
[DisplayType.StackedBar]: 'Bar',
[DisplayType.Table]: 'Table',
[DisplayType.Pie]: 'Pie',
[DisplayType.Bar]: 'Bar',
[DisplayType.Number]: 'Number',
[DisplayType.Search]: 'Search',
[DisplayType.Heatmap]: 'Heatmap',
[DisplayType.Markdown]: 'Markdown',

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.

P2 StackedBar and Bar share the same label, producing confusing error messages

Both DisplayType.StackedBar and DisplayType.Bar are mapped to 'Bar'. When validateDisplayType rejects a time bucket on a non-time-series chart (e.g. displayType === DisplayType.Bar), the error reads "Time bucketing is only supported for Time Series and Bar charts, not Bar charts." — the user sees "not Bar charts" for a Bar-chart input, which is contradictory and unhelpful. Consider a distinct label such as 'Stacked Bar' for StackedBar.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Conductor Fix in Cursor Fix in Codex

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant