mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
[HDX-3277] Fix service filter quote escaping on Services page (#1931)
## Summary - escape service name values when generating the Services page SQL filter to prevent malformed queries when names contain quotes - switch from string interpolation to `SqlString.format` with a raw left-hand expression and escaped right-hand value ## Why - service names containing apostrophes/single quotes broke ClickHouse query parsing, causing the Services page to error Linear: https://linear.app/clickhouse/issue/HDX-3277/service-page-quote-escape-bug
This commit is contained in:
parent
2b53b8e9ab
commit
134f1dca47
3 changed files with 44 additions and 1 deletions
5
.changeset/hdx-3277-service-quote-escape.md
Normal file
5
.changeset/hdx-3277-service-quote-escape.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@hyperdx/app': patch
|
||||
---
|
||||
|
||||
fix: escape service filter values on Services page to handle quoted names safely
|
||||
|
|
@ -8,6 +8,7 @@ import {
|
|||
useQueryStates,
|
||||
} from 'nuqs';
|
||||
import { UseControllerProps, useForm, useWatch } from 'react-hook-form';
|
||||
import SqlString from 'sqlstring';
|
||||
import { tcFromSource } from '@hyperdx/common-utils/dist/core/metadata';
|
||||
import { convertDateRangeToGranularityString } from '@hyperdx/common-utils/dist/core/utils';
|
||||
import {
|
||||
|
|
@ -90,6 +91,13 @@ type AppliedConfig = AppliedConfigParams & {
|
|||
|
||||
const MAX_NUM_SERIES = HARD_LINES_LIMIT;
|
||||
|
||||
export function buildInFilterCondition(
|
||||
columnExpression: string,
|
||||
value: string,
|
||||
): string {
|
||||
return SqlString.format('? IN (?)', [SqlString.raw(columnExpression), value]);
|
||||
}
|
||||
|
||||
function getScopedFilters({
|
||||
appliedConfig,
|
||||
expressions,
|
||||
|
|
@ -112,7 +120,10 @@ function getScopedFilters({
|
|||
if (appliedConfig.service) {
|
||||
filters.push({
|
||||
type: 'sql',
|
||||
condition: `${expressions.service} IN ('${appliedConfig.service}')`,
|
||||
condition: buildInFilterCondition(
|
||||
expressions.service,
|
||||
appliedConfig.service,
|
||||
),
|
||||
});
|
||||
}
|
||||
if (includeNonEmptyEndpointFilter) {
|
||||
|
|
|
|||
27
packages/app/src/__tests__/ServicesDashboardPage.test.ts
Normal file
27
packages/app/src/__tests__/ServicesDashboardPage.test.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { buildInFilterCondition } from '../ServicesDashboardPage';
|
||||
|
||||
describe('buildInFilterCondition', () => {
|
||||
it.each([
|
||||
{
|
||||
columnExpression: 'ServiceName',
|
||||
value: 'checkout-service',
|
||||
expected: "ServiceName IN ('checkout-service')",
|
||||
},
|
||||
{
|
||||
columnExpression: "SpanAttributes['service.name']",
|
||||
value: "O'Reilly API",
|
||||
expected: "SpanAttributes['service.name'] IN ('O\\'Reilly API')",
|
||||
},
|
||||
{
|
||||
columnExpression: "ResourceAttributes['service.namespace']",
|
||||
value: 'payments "v2"',
|
||||
expected:
|
||||
"ResourceAttributes['service.namespace'] IN ('payments \\\"v2\\\"')",
|
||||
},
|
||||
])(
|
||||
'escapes value and keeps column expression for $columnExpression',
|
||||
({ columnExpression, value, expected }) => {
|
||||
expect(buildInFilterCondition(columnExpression, value)).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
Loading…
Reference in a new issue