mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
fix: Preserve original select from time chart event selection (#1277)
FIxes: HDX-2606
This commit is contained in:
parent
6262ced8ce
commit
658728318b
5 changed files with 113 additions and 3 deletions
6
.changeset/popular-rabbits-argue.md
Normal file
6
.changeset/popular-rabbits-argue.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
"@hyperdx/app": patch
|
||||
"@hyperdx/common-utils": patch
|
||||
---
|
||||
|
||||
fix: Preserve original select from time chart event selection
|
||||
|
|
@ -1145,9 +1145,17 @@ function DBSearchPage() {
|
|||
dateRange: searchedTimeRange,
|
||||
displayType: DisplayType.StackedBar,
|
||||
with: aliasWith,
|
||||
// Preserve the original table select string for "View Events" links
|
||||
eventTableSelect: searchedConfig.select,
|
||||
...variableConfig,
|
||||
};
|
||||
}, [chartConfig, searchedSource, aliasWith, searchedTimeRange]);
|
||||
}, [
|
||||
chartConfig,
|
||||
searchedSource,
|
||||
aliasWith,
|
||||
searchedTimeRange,
|
||||
searchedConfig.select,
|
||||
]);
|
||||
|
||||
const onFormSubmit = useCallback<FormEventHandler<HTMLFormElement>>(
|
||||
e => {
|
||||
|
|
|
|||
|
|
@ -168,14 +168,21 @@ function DBTimeChartComponent({
|
|||
where = config.select[0].aggCondition ?? '';
|
||||
whereLanguage = config.select[0].aggConditionLanguage ?? 'lucene';
|
||||
}
|
||||
return new URLSearchParams({
|
||||
const params: Record<string, string> = {
|
||||
source: (isMetricChart ? source?.logSourceId : source?.id) ?? '',
|
||||
where: where,
|
||||
whereLanguage: whereLanguage,
|
||||
filters: JSON.stringify(config.filters),
|
||||
from: from.toString(),
|
||||
to: to.toString(),
|
||||
});
|
||||
};
|
||||
// Include the select parameter if provided to preserve custom columns
|
||||
// eventTableSelect is used for charts that override select (like histograms with count)
|
||||
// to preserve the original table's select expression
|
||||
if (config.eventTableSelect) {
|
||||
params.select = config.eventTableSelect;
|
||||
}
|
||||
return new URLSearchParams(params);
|
||||
}, [clickedActiveLabelDate, config, granularity, source]);
|
||||
|
||||
return isLoading && !data ? (
|
||||
|
|
@ -269,6 +276,7 @@ function DBTimeChartComponent({
|
|||
}}
|
||||
>
|
||||
<Link
|
||||
data-testid="chart-view-events-link"
|
||||
href={`/search?${qparams?.toString()}`}
|
||||
className="text-white-hover text-decoration-none"
|
||||
onClick={() => setActiveClickPayload(undefined)}
|
||||
|
|
|
|||
|
|
@ -259,5 +259,91 @@ test.describe('Search', { tag: '@search' }, () => {
|
|||
expect(rowCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
test('Histogram drag-to-zoom preserves custom SELECT columns', async ({
|
||||
page,
|
||||
}) => {
|
||||
const CUSTOM_SELECT =
|
||||
'Timestamp, ServiceName, Body as message, SeverityText';
|
||||
|
||||
await test.step('Perform initial search', async () => {
|
||||
await expect(page.locator('[data-testid="search-form"]')).toBeVisible();
|
||||
await page.locator('[data-testid="search-submit-button"]').click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
await test.step('Setup custom SELECT columns', async () => {
|
||||
// The SELECT field is the first CodeMirror editor (index 0)
|
||||
const selectEditor = page.locator('.cm-content').first();
|
||||
await expect(selectEditor).toBeVisible();
|
||||
|
||||
// Select all and replace with custom columns
|
||||
await selectEditor.click({ clickCount: 3 });
|
||||
await page.keyboard.type(CUSTOM_SELECT);
|
||||
});
|
||||
|
||||
await test.step('Search with custom columns and wait for histogram', async () => {
|
||||
await page.locator('[data-testid="search-submit-button"]').click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Wait for histogram to render with data
|
||||
await expect(
|
||||
page.locator('.recharts-responsive-container').first(),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
await test.step('Drag on histogram to select time range', async () => {
|
||||
const chartSurface = page.locator('.recharts-surface').first();
|
||||
await expect(chartSurface).toBeVisible();
|
||||
|
||||
const box = await chartSurface.boundingBox();
|
||||
expect(box).toBeTruthy();
|
||||
|
||||
// Drag from 25% to 75% of chart width to zoom into a time range
|
||||
const startX = box!.x + box!.width * 0.25;
|
||||
const endX = box!.x + box!.width * 0.75;
|
||||
const y = box!.y + box!.height / 2;
|
||||
|
||||
await page.mouse.move(startX, y);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(endX, y, { steps: 10 });
|
||||
await page.mouse.up();
|
||||
|
||||
// Wait for the zoom operation to complete
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
await test.step('Verify custom SELECT columns are preserved', async () => {
|
||||
// Check URL parameters
|
||||
const url = page.url();
|
||||
expect(url, 'URL should contain select parameter').toContain('select=');
|
||||
expect(url, 'URL should contain alias "message"').toContain('message');
|
||||
|
||||
// Verify SELECT editor content
|
||||
const selectEditor = page.locator('.cm-content').first();
|
||||
await expect(selectEditor).toBeVisible();
|
||||
const selectValue = await selectEditor.textContent();
|
||||
|
||||
expect(selectValue, 'SELECT should contain alias').toContain(
|
||||
'Body as message',
|
||||
);
|
||||
expect(selectValue, 'SELECT should contain SeverityText').toContain(
|
||||
'SeverityText',
|
||||
);
|
||||
});
|
||||
|
||||
await test.step('Verify search results are still displayed', async () => {
|
||||
const searchResultsTable = page.locator(
|
||||
'[data-testid="search-results-table"]',
|
||||
);
|
||||
await expect(
|
||||
searchResultsTable,
|
||||
'Search results table should be visible',
|
||||
).toBeVisible();
|
||||
|
||||
const rowCount = await searchResultsTable.locator('tr').count();
|
||||
expect(rowCount, 'Should have search results').toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -371,6 +371,8 @@ export const _ChartConfigSchema = z.object({
|
|||
selectGroupBy: z.boolean().optional(),
|
||||
metricTables: MetricTableSchema.optional(),
|
||||
seriesReturnType: z.enum(['ratio', 'column']).optional(),
|
||||
// Used to preserve original table select string when chart overrides it (e.g., histograms)
|
||||
eventTableSelect: z.string().optional(),
|
||||
});
|
||||
|
||||
// This is a ChartConfig type without the `with` CTE clause included.
|
||||
|
|
|
|||
Loading…
Reference in a new issue