Skip to content

Commit 0ee967f

Browse files
committed
fix: Preserve original select from time chart event selection
FIxes: HDX-2606
1 parent 3c8f3b5 commit 0ee967f

File tree

4 files changed

+100
-3
lines changed

4 files changed

+100
-3
lines changed

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 || chartConfig.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: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,24 @@ 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 (
183+
config.eventTableSelect &&
184+
typeof config.eventTableSelect === 'string'
185+
) {
186+
params.select = config.eventTableSelect;
187+
}
188+
return new URLSearchParams(params);
179189
}, [clickedActiveLabelDate, config, granularity, source]);
180190

181191
return isLoading && !data ? (
@@ -269,6 +279,7 @@ function DBTimeChartComponent({
269279
}}
270280
>
271281
<Link
282+
data-testid="chart-view-events-link"
272283
href={`/search?${qparams?.toString()}`}
273284
className="text-white-hover text-decoration-none"
274285
onClick={() => setActiveClickPayload(undefined)}

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

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

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 when chart overrides it (e.g., histograms)
375+
eventTableSelect: SelectListSchema.optional(),
374376
});
375377

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

0 commit comments

Comments
 (0)