diff --git a/packages/app/tests/e2e/components/ChartEditorComponent.ts b/packages/app/tests/e2e/components/ChartEditorComponent.ts index 2b1f5199dc..85ee50e1c9 100644 --- a/packages/app/tests/e2e/components/ChartEditorComponent.ts +++ b/packages/app/tests/e2e/components/ChartEditorComponent.ts @@ -3,9 +3,10 @@ * Used for creating and configuring dashboard tiles and chart explorer */ import { DisplayType } from '@hyperdx/common-utils/dist/types'; -import { Locator, Page } from '@playwright/test'; +import { expect, Locator, Page } from '@playwright/test'; import { dismissSqlAutocomplete, getSqlEditor } from '../utils/locators'; +import { switchWhereToLucene } from '../utils/lucene-autocomplete'; import { WebhookAlertModalComponent } from './WebhookAlertModalComponent'; @@ -100,6 +101,39 @@ export class ChartEditorComponent { await dismissSqlAutocomplete(this.page); } + /** + * The tile-editor modal. Series-level lookups have to be scoped to it: the + * dashboard behind it renders its own WHERE input with identical markup. + * Only meaningful on a dashboard — the chart explorer has no modal. + */ + private get tileEditor() { + return this.page + .getByRole('dialog') + .filter({ has: this.page.getByTestId('chart-name-input') }); + } + + /** + * Set the tile's own filter, which a time chart stores as its series' + * `aggCondition` rather than in the config's statement-level `where`. + * + * Switches to Lucene first for two reasons: the mode is sticky (it is kept in + * localStorage, so a previous spec can leave it on SQL), and + * `series-where-input` exists only on the Lucene branch of SearchWhereInput — + * the SQL branch renders a CodeMirror that carries no test id. + */ + async setSeriesWhere(condition: string) { + const editor = this.tileEditor; + await switchWhereToLucene(editor.getByTestId('where-language-switch')); + + const input = editor.getByTestId('series-where-input'); + await expect(input).toBeVisible(); + await input.fill(condition); + // Blur to close the suggestion dropdown, which otherwise overlays the + // Run/Save buttons and fails their actionability check. Escape would bubble + // to the tile-editor modal and close it. + await input.blur(); + } + /** * Select a data source */ diff --git a/packages/app/tests/e2e/features/release-markers.spec.ts b/packages/app/tests/e2e/features/release-markers.spec.ts index 01e1450fe7..1b96ae213f 100644 --- a/packages/app/tests/e2e/features/release-markers.spec.ts +++ b/packages/app/tests/e2e/features/release-markers.spec.ts @@ -144,6 +144,35 @@ test.describe('Release markers', { tag: ['@full-stack', '@dashboard'] }, () => { await expect(page).not.toHaveURL(/releaseMarkers=true/); }); + // The test above filters the *dashboard*; this one filters the *tile*. They + // are different fields — a dashboard filter travels as `filters`, while a + // time chart's own filter is stored as its series' `aggCondition`, not in the + // config's statement-level `where` (the editor clears that). Reading only + // `where` left every time chart's markers unscoped, and because the resulting + // markers then spanned two services with no matching line, they were all + // suppressed: filter to one service, see nothing. + test("scopes markers to the tile's own filter", async ({ page }) => { + await seedReleases(); + + await dashboardPage.createNewDashboard(); + await dashboardPage.addTileWithSource( + 'Tile-filtered markers', + DEFAULT_LOGS_SOURCE_NAME, + `ServiceName:"${RELEASE_SERVICE}"`, + ); + // No dashboard-wide filter this time: the tile's own filter has to be what + // narrows the releases query. + await dashboardPage.toggleReleaseAnnotations(); + + await expect(page).toHaveURL(/releaseMarkers=true/); + + const labels = dashboardPage.getAnnotationLabels(); + await expect(dashboardPage.getAnnotationMarkers()).toHaveCount(2); + await expect(labels.filter({ hasText: OLD_VERSION })).toBeVisible(); + await expect(labels.filter({ hasText: NEW_VERSION })).toBeVisible(); + await expect(labels.filter({ hasText: OTHER_VERSION })).toHaveCount(0); + }); + // Colour alone can't identify a service once the legend overflows its 4-item // cap and the matching entry hides behind "+N more". Hover has to name the // service outright. diff --git a/packages/app/tests/e2e/page-objects/DashboardPage.ts b/packages/app/tests/e2e/page-objects/DashboardPage.ts index 31284350ee..1a2b0d09b8 100644 --- a/packages/app/tests/e2e/page-objects/DashboardPage.ts +++ b/packages/app/tests/e2e/page-objects/DashboardPage.ts @@ -589,13 +589,24 @@ export class DashboardPage { * Add a tile bound to an explicit source. Unlike addTileWithConfig (which * relies on the editor's default source), this selects a known source so the * exported tile carries a deterministic source name that auto-maps on import. + * + * `seriesWhere` sets the tile's own filter, which a time chart stores as its + * series' `aggCondition` — a different field from the dashboard-wide filter + * that `setGlobalFilter` drives. */ - async addTileWithSource(chartName: string, sourceName: string) { + async addTileWithSource( + chartName: string, + sourceName: string, + seriesWhere?: string, + ) { await this.addTile(); await expect(this.chartEditor.nameInput).toBeVisible(); await this.chartEditor.waitForDataToLoad(); await this.chartEditor.setChartName(chartName); await this.chartEditor.selectSource(sourceName); + if (seriesWhere != null) { + await this.chartEditor.setSeriesWhere(seriesWhere); + } await this.chartEditor.runQuery(false); await this.chartEditor.save(); await expect(this.getTiles()).toHaveCount(1, { timeout: 10000 });