Repository navigation
fix(app): serialize JSON column filters with dot access (HDX-5085) #2864
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@hyperdx/app': patch | ||
| --- | ||
|
|
||
| Fix search page filters on JSON columns generating invalid bracket access. Adding or excluding a value on a sub-key of a JSON-typed column (e.g. `ResourceAttributes.region`) previously serialized to `ResourceAttributes['region']`, which ClickHouse rejects on a JSON column ("First argument for function 'arrayElement' must be array, got 'JSON'"). Sub-keys of JSON columns now serialize with dot access (`ResourceAttributes.\`region\``). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -165,3 +165,79 @@ describe('parsed-JSON "Add to Filters" -> valid SQL pipeline (HDX-4427)', () => | |
| expect(entry.included).toEqual(new Set(["O'Brien"])); | ||
| }); | ||
| }); | ||
|
|
||
| // HDX-5085: filtering a sub-key of a *native* JSON column (e.g. from clicking | ||
| // "Add to Filters" then "Exclude" in the sidebar) must serialize as dot access, | ||
| // not the bracket access ClickHouse rejects on a JSON column with "First | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The new pipeline tests, along with the added cases in Context Used: AGENTS.md (source) 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! |
||
| // argument for function 'arrayElement' must be array, got 'JSON'". | ||
| describe('JSON-column sub-key filter -> valid dot-access SQL pipeline (HDX-5085)', () => { | ||
| // ResourceAttributes is a native JSON column here (unlike the Map columns | ||
| // above), so its sub-keys must render with dot access. | ||
| const jsonKnownColumns = new Set(['ResourceAttributes', 'ServiceName']); | ||
| const jsonColumns = new Set(['ResourceAttributes']); | ||
|
|
||
| const runJsonFilter = ( | ||
| key: string, | ||
| values: { included?: string[]; excluded?: string[] }, | ||
| ) => { | ||
| const state: FilterState = { | ||
| [key]: { | ||
| included: new Set<string | boolean>(values.included ?? []), | ||
| excluded: new Set<string | boolean>(values.excluded ?? []), | ||
| }, | ||
| }; | ||
| return filtersToQuery( | ||
| escapeFilterStateKeys(state, jsonKnownColumns, jsonColumns), | ||
| ); | ||
| }; | ||
|
|
||
| it('excludes a JSON sub-key value with dot access, not bracket access', () => { | ||
| expect( | ||
| runJsonFilter('ResourceAttributes.region', { | ||
| excluded: ['eu-central-1'], | ||
| }), | ||
| ).toEqual([ | ||
| { | ||
| type: 'sql', | ||
| condition: "ResourceAttributes.`region` NOT IN ('eu-central-1')", | ||
| }, | ||
| ]); | ||
| }); | ||
|
|
||
| it('never emits the illegal bracket subscript on a JSON column', () => { | ||
| const query = runJsonFilter('ResourceAttributes.region', { | ||
| included: ['us-east-1'], | ||
| excluded: ['eu-central-1'], | ||
| }); | ||
| expect(query.length).toBeGreaterThan(0); | ||
| for (const filter of query) { | ||
| expect(filter.type).toBe('sql'); | ||
| if (filter.type === 'sql') { | ||
| expect(filter.condition).not.toContain("ResourceAttributes['"); | ||
| expect(isValidFilterCondition(filter.condition, 'sql')).toBe(true); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| it('renders a nested JSON path with backtick-quoted segments', () => { | ||
| expect( | ||
| runJsonFilter('ResourceAttributes.host.name', { included: ['web-1'] }), | ||
| ).toEqual([ | ||
| { | ||
| type: 'sql', | ||
| condition: "ResourceAttributes.`host`.`name` IN ('web-1')", | ||
| }, | ||
| ]); | ||
| }); | ||
|
|
||
| it('round-trips the JSON sub-key back to its clean dot form', () => { | ||
| const query = runJsonFilter('ResourceAttributes.region', { | ||
| excluded: ['eu-central-1'], | ||
| }); | ||
| const back = parseQuery(query).filters; | ||
| const restoredKeys = Object.keys(back).map(cleanClickHouseExpression); | ||
| expect(restoredKeys).toEqual(['ResourceAttributes.region']); | ||
| const entry = back[Object.keys(back)[0]]; | ||
| expect(entry.excluded).toEqual(new Set(['eu-central-1'])); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When JSON-column metadata is loading or fails,
jsonColumns ?? []treats every column as non-JSON while facet and Load more queries remain enabled. Native JSON sub-keys are therefore serialized with Map bracket access, causing ClickHouse to reject the facet query with anarrayElement-on-JSON error; the initial request shows an error notification, while Load more silently returns no additional values.