Skip to content

Commit 6587283

Browse files
authored
fix: Preserve original select from time chart event selection (#1277)
FIxes: HDX-2606
1 parent 6262ced commit 6587283

File tree

5 files changed

+113
-3
lines changed

5 files changed

+113
-3
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@hyperdx/app": patch
3+
"@hyperdx/common-utils": patch
4+
---
5+
6+
fix: Preserve original select from time chart event selection

packages/app/src/DBSearchPage.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1145,9 +1145,17 @@ function DBSearchPage() {
11451145
dateRange: searchedTimeRange,
11461146
displayType: DisplayType.StackedBar,
11471147
with: aliasWith,
1148+
// Preserve the original table select string for "View Events" links
1149+
eventTableSelect: searchedConfig.select,
11481150
...variableConfig,
11491151
};
1150-
}, [chartConfig, searchedSource, aliasWith, searchedTimeRange]);
1152+
}, [
1153+
chartConfig,
1154+
searchedSource,
1155+
aliasWith,
1156+
searchedTimeRange,
1157+
searchedConfig.select,
1158+
]);
11511159

11521160
const onFormSubmit = useCallback<FormEventHandler<HTMLFormElement>>(
11531161
e => {

packages/app/src/components/DBTimeChart.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,21 @@ function DBTimeChartComponent({
168168
where = config.select[0].aggCondition ?? '';
169169
whereLanguage = config.select[0].aggConditionLanguage ?? 'lucene';
170170
}
171-
return new URLSearchParams({
171+
const params: Record<string, string> = {
172172
source: (isMetricChart ? source?.logSourceId : source?.id) ?? '',
173173
where: where,
174174
whereLanguage: whereLanguage,
175175
filters: JSON.stringify(config.filters),
176176
from: from.toString(),
177177
to: to.toString(),
178-
});
178+
};
179+
// Include the select parameter if provided to preserve custom columns
180+
// eventTableSelect is used for charts that override select (like histograms with count)
181+
// to preserve the original table's select expression
182+
if (config.eventTableSelect) {
183+
params.select = config.eventTableSelect;
184+
}
185+
return new URLSearchParams(params);
179186
}, [clickedActiveLabelDate, config, granularity, source]);
180187

181188
return isLoading && !data ? (
@@ -269,6 +276,7 @@ function DBTimeChartComponent({
269276
}}
270277
>
271278
<Link
279+
data-testid="chart-view-events-link"
272280
href={`/search?${qparams?.toString()}`}
273281
className="text-white-hover text-decoration-none"
274282
onClick={() => setActiveClickPayload(undefined)}

packages/app/tests/e2e/features/search/search.spec.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,5 +259,91 @@ test.describe('Search', { tag: '@search' }, () => {
259259
expect(rowCount).toBeGreaterThan(0);
260260
});
261261
});
262+
263+
test('Histogram drag-to-zoom preserves custom SELECT columns', async ({
264+
page,
265+
}) => {
266+
const CUSTOM_SELECT =
267+
'Timestamp, ServiceName, Body as message, SeverityText';
268+
269+
await test.step('Perform initial search', async () => {
270+
await expect(page.locator('[data-testid="search-form"]')).toBeVisible();
271+
await page.locator('[data-testid="search-submit-button"]').click();
272+
await page.waitForLoadState('networkidle');
273+
});
274+
275+
await test.step('Setup custom SELECT columns', async () => {
276+
// The SELECT field is the first CodeMirror editor (index 0)
277+
const selectEditor = page.locator('.cm-content').first();
278+
await expect(selectEditor).toBeVisible();
279+
280+
// Select all and replace with custom columns
281+
await selectEditor.click({ clickCount: 3 });
282+
await page.keyboard.type(CUSTOM_SELECT);
283+
});
284+
285+
await test.step('Search with custom columns and wait for histogram', async () => {
286+
await page.locator('[data-testid="search-submit-button"]').click();
287+
await page.waitForLoadState('networkidle');
288+
289+
// Wait for histogram to render with data
290+
await expect(
291+
page.locator('.recharts-responsive-container').first(),
292+
).toBeVisible();
293+
});
294+
295+
await test.step('Drag on histogram to select time range', async () => {
296+
const chartSurface = page.locator('.recharts-surface').first();
297+
await expect(chartSurface).toBeVisible();
298+
299+
const box = await chartSurface.boundingBox();
300+
expect(box).toBeTruthy();
301+
302+
// Drag from 25% to 75% of chart width to zoom into a time range
303+
const startX = box!.x + box!.width * 0.25;
304+
const endX = box!.x + box!.width * 0.75;
305+
const y = box!.y + box!.height / 2;
306+
307+
await page.mouse.move(startX, y);
308+
await page.mouse.down();
309+
await page.mouse.move(endX, y, { steps: 10 });
310+
await page.mouse.up();
311+
312+
// Wait for the zoom operation to complete
313+
await page.waitForLoadState('networkidle');
314+
});
315+
316+
await test.step('Verify custom SELECT columns are preserved', async () => {
317+
// Check URL parameters
318+
const url = page.url();
319+
expect(url, 'URL should contain select parameter').toContain('select=');
320+
expect(url, 'URL should contain alias "message"').toContain('message');
321+
322+
// Verify SELECT editor content
323+
const selectEditor = page.locator('.cm-content').first();
324+
await expect(selectEditor).toBeVisible();
325+
const selectValue = await selectEditor.textContent();
326+
327+
expect(selectValue, 'SELECT should contain alias').toContain(
328+
'Body as message',
329+
);
330+
expect(selectValue, 'SELECT should contain SeverityText').toContain(
331+
'SeverityText',
332+
);
333+
});
334+
335+
await test.step('Verify search results are still displayed', async () => {
336+
const searchResultsTable = page.locator(
337+
'[data-testid="search-results-table"]',
338+
);
339+
await expect(
340+
searchResultsTable,
341+
'Search results table should be visible',
342+
).toBeVisible();
343+
344+
const rowCount = await searchResultsTable.locator('tr').count();
345+
expect(rowCount, 'Should have search results').toBeGreaterThan(0);
346+
});
347+
});
262348
});
263349
});

packages/common-utils/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,8 @@ export const _ChartConfigSchema = z.object({
371371
selectGroupBy: z.boolean().optional(),
372372
metricTables: MetricTableSchema.optional(),
373373
seriesReturnType: z.enum(['ratio', 'column']).optional(),
374+
// Used to preserve original table select string when chart overrides it (e.g., histograms)
375+
eventTableSelect: z.string().optional(),
374376
});
375377

376378
// This is a ChartConfig type without the `with` CTE clause included.

0 commit comments

Comments
 (0)