From 1243f234a86d15ecad6bd2017657f29d2de7779b Mon Sep 17 00:00:00 2001 From: Cesare de Cal Date: Thu, 5 Feb 2026 17:41:56 +0100 Subject: [PATCH] [FTR to Scout] Migrate `x-pack` dashboard `group2` tests (#249233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR migrates the all FTR tests belonging to the `x-pack/platform/test/functional/apps/dashboard/group2/config.ts` config to Scout. ## Migration All tests were migrated and older tests deleted. We still maintain the **same** test coverage. ## Results * **Scout tests are over 4x faster** tests than FTR 🚀 Total runtime: Scout (**7 seconds**) vs FTR (**29 seconds**) * Most of these tests are designed to run on **both local deployments** and **Elastic Cloud** ("deployment-agnostic"). The FTR tests only ran on local stateful. Scout makes this easy to achieve: we just need to write the test once and add the appropriate tags! ### Why are Scout tests faster? * Scout uses two parallel workers to run the tests. * We ingest Elasticsearch archives just once, in the [global setup hook](https://docs.elastic.dev/appex-qa/scout/global-setup-hook), before test execution start. * FTR test cases that shared identical parts of the user flow were merged into a single Scout test case. * We keep [Scout best practices](https://docs.elastic.dev/appex-qa/scout/best-practices) in mind: we use Playwright auto-waiting, we design page objects to be performant, and more. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit 50444e3cb36fb11251a31e8a34be877ab0938f80) Co-authored-by: Cursor # Conflicts: # .buildkite/scout_ci_config.yml # src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts # src/platform/packages/shared/kbn-scout/src/playwright/page_objects/discover_app.ts # src/platform/plugins/shared/dashboard/moon.yml # src/platform/plugins/shared/dashboard/tsconfig.json # src/platform/plugins/shared/data/public/search/session/sessions_mgmt/lib/documentation.ts # x-pack/platform/test/functional/apps/dashboard/group2/_async_dashboard.ts # x-pack/platform/test/functional/apps/dashboard/group2/dashboard_panel_listing.ts # x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts # x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts # x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts # x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts --- .buildkite/ftr_platform_stateful_configs.yml | 1 - .buildkite/scout_ci_config.yml | 1 + .../playwright/eui_components/combo_box.ts | 11 +- .../scope/worker/apis/data_views/index.ts | 65 +- .../fixtures/scope/worker/apis/index.ts | 4 + .../scope/worker/apis/sample_data/index.ts | 46 + .../playwright/page_objects/dashboard_app.ts | 819 +++++++++++++++++- .../playwright/page_objects/date_picker.ts | 42 +- .../playwright/page_objects/discover_app.ts | 59 +- .../{fiter_bar.ts => filter_bar.ts} | 6 +- .../src/playwright/page_objects/index.ts | 5 +- .../src/playwright/page_objects/lens_app.ts | 202 +++++ .../src/playwright/page_objects/maps_page.ts | 65 +- .../plugins/shared/dashboard/moon.yml | 3 + .../dashboard/test/scout/ui/constants.ts | 90 ++ .../scout/ui/parallel.playwright.config.ts | 16 + .../ui/parallel_tests/async_dashboard.spec.ts | 94 ++ .../dashboard_lens_by_value.spec.ts | 122 +++ .../dashboard_maps_by_value.spec.ts | 134 +++ .../dashboard_panel_listing.spec.ts | 63 ++ .../dashboard_panel_listing_obs_group.spec.ts | 62 ++ .../dashboard_search_by_value.spec.ts | 81 ++ .../scout/ui/parallel_tests/global.setup.ts | 31 + .../controls_migration_smoke.spec.ts | 125 +++ ...ntrols_dashboard_migration_test_8_0_0.json | 5 + ...rols_dashboard_migration_test_8_0_0.ndjson | 0 .../lens_dashboard_migration_test_7_12_1.json | 13 + ...ens_dashboard_migration_test_7_12_1.ndjson | 0 .../tsvb_dashboard_migration_test_7_12_1.json | 5 + ...svb_dashboard_migration_test_7_12_1.ndjson | 0 .../tsvb_dashboard_migration_test_7_13_3.json | 5 + ...svb_dashboard_migration_test_7_13_3.ndjson | 0 ...alize_dashboard_migration_test_7_12_1.json | 13 + ...ize_dashboard_migration_test_7_12_1.ndjson | 0 .../lens_migration_smoke.spec.ts | 92 ++ .../tsvb_migration_smoke_7_12_1.spec.ts | 91 ++ .../tsvb_migration_smoke_7_13_3.spec.ts | 72 ++ .../visualize_migration_smoke.spec.ts | 90 ++ .../parallel_tests/panel_time_range.spec.ts | 148 ++++ .../ui/parallel_tests/panel_titles.spec.ts | 296 +++++++ .../ui/parallel_tests/sync_colors.spec.ts | 189 ++++ .../scout/utils/migration_smoke_helpers.ts | 88 ++ .../plugins/shared/dashboard/tsconfig.json | 6 +- .../apps/dashboard/group2/_async_dashboard.ts | 198 ----- .../apps/dashboard/group2/config.ts | 17 - .../group2/dashboard_lens_by_value.ts | 108 --- .../group2/dashboard_maps_by_value.ts | 139 --- .../group2/dashboard_panel_listing.ts | 75 -- .../group2/dashboard_search_by_value.ts | 86 -- .../functional/apps/dashboard/group2/index.ts | 26 - .../controls_migration_smoke_test.ts | 114 --- .../lens_migration_smoke_test.ts | 90 -- .../tsvb_migration_smoke_test.ts | 141 --- .../visualize_migration_smoke_test.ts | 90 -- .../apps/dashboard/group2/panel_time_range.ts | 85 -- .../apps/dashboard/group2/panel_titles.ts | 170 ---- .../apps/dashboard/group2/sync_colors.ts | 165 ---- 57 files changed, 3158 insertions(+), 1606 deletions(-) create mode 100644 src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/sample_data/index.ts rename src/platform/packages/shared/kbn-scout/src/playwright/page_objects/{fiter_bar.ts => filter_bar.ts} (93%) create mode 100644 src/platform/packages/shared/kbn-scout/src/playwright/page_objects/lens_app.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/constants.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel.playwright.config.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/async_dashboard.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_lens_by_value.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_maps_by_value.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_panel_listing.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_panel_listing_obs_group.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_search_by_value.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/global.setup.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/controls_migration_smoke.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.json rename {x-pack/platform/test/functional/apps/dashboard/group2 => src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests}/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.ndjson (100%) create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.json rename {x-pack/platform/test/functional/apps/dashboard/group2 => src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests}/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.ndjson (100%) create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.json rename {x-pack/platform/test/functional/apps/dashboard/group2 => src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests}/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.ndjson (100%) create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.json rename {x-pack/platform/test/functional/apps/dashboard/group2 => src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests}/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.ndjson (100%) create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.json rename {x-pack/platform/test/functional/apps/dashboard/group2 => src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests}/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.ndjson (100%) create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/lens_migration_smoke.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/tsvb_migration_smoke_7_12_1.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/tsvb_migration_smoke_7_13_3.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/visualize_migration_smoke.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/panel_time_range.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/panel_titles.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/sync_colors.spec.ts create mode 100644 src/platform/plugins/shared/dashboard/test/scout/utils/migration_smoke_helpers.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/_async_dashboard.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/config.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/dashboard_panel_listing.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/index.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/panel_time_range.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/panel_titles.ts delete mode 100644 x-pack/platform/test/functional/apps/dashboard/group2/sync_colors.ts diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index 833ebe30d2674..6391ee888e0fc 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -211,7 +211,6 @@ enabled: - x-pack/platform/test/functional/apps/canvas/config.ts - x-pack/platform/test/functional/apps/cross_cluster_replication/config.ts - x-pack/platform/test/functional/apps/dashboard/group1/config.ts - - x-pack/platform/test/functional/apps/dashboard/group2/config.ts - x-pack/platform/test/functional/apps/dashboard/group3/config.ts - x-pack/platform/test/functional/apps/data_views/config.ts - x-pack/platform/test/functional/apps/dev_tools/config.ts diff --git a/.buildkite/scout_ci_config.yml b/.buildkite/scout_ci_config.yml index d204f941de8d8..f9339edd702d2 100644 --- a/.buildkite/scout_ci_config.yml +++ b/.buildkite/scout_ci_config.yml @@ -15,6 +15,7 @@ plugins: - spaces - streams_app - transform + - dashboard disabled: packages: diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/combo_box.ts b/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/combo_box.ts index 678c42a00e54a..b308251565db7 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/combo_box.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/eui_components/combo_box.ts @@ -147,11 +147,18 @@ export class EuiComboBoxWrapper { } // Select a single option in the comboBox - async selectSingleOption(value: string) { + async selectSingleOption( + value: string, + options: { optionTestSubj?: string; optionRoleName?: string } = {} + ) { await this.clear(); await this.comboBoxMainInput.click(); await this.typeValueInSearch(value); - await this.page.getByRole('option', { name: value }).click(); + // Prefer a specific test subj when option text is ambiguous. + const optionLocator = options.optionTestSubj + ? this.page.testSubj.locator(options.optionTestSubj) + : this.page.getByRole('option', { name: options.optionRoleName ?? value, exact: false }); + await optionLocator.click(); expect(await this.getSelectedValue()).toBe(value); } diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/data_views/index.ts b/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/data_views/index.ts index ce1a13c40e5aa..28d648cbc8338 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/data_views/index.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/data_views/index.ts @@ -28,36 +28,48 @@ export interface DataViewsApiService { * Get all data views * @returns Promise with array of data views and status */ - getAll: () => Promise>; + getAll: (spaceId?: string) => Promise>; /** * Get a single data view by ID * @param id - The data view ID + * @param spaceId - Optional space ID * @returns Promise with the data view and status */ - get: (id: string) => Promise>; + get: (id: string, spaceId?: string) => Promise>; /** * Delete a data view by ID * @param id - The data view ID to delete + * @param spaceId - Optional space ID * @returns Promise with status code */ - delete: (id: string) => Promise; + delete: (id: string, spaceId?: string) => Promise; /** * Find data views that match a predicate function * @param predicate - Function to filter data views + * @param spaceId - Optional space ID * @returns Promise with filtered array of data views and status */ - find: (predicate: (dataView: DataView) => boolean) => Promise>; + find: ( + predicate: (dataView: DataView) => boolean, + spaceId?: string + ) => Promise>; /** * Delete a data view by its title (convenience method) * Finds the first data view matching the title and deletes it * @param title - The data view title to search for + * @param spaceId - Optional space ID * @returns Promise with status code */ - deleteByTitle: (title: string) => Promise; + deleteByTitle: (title: string, spaceId?: string) => Promise; + + /** + * Get data view ID by title (optionally within a space) + */ + getIdByTitle: (title: string, spaceId?: string) => Promise; } /** @@ -70,15 +82,17 @@ export const getDataViewsApiHelper = ( log: ScoutLogger, kbnClient: KbnClient ): DataViewsApiService => { + const withSpace = (path: string, spaceId?: string) => (spaceId ? `/s/${spaceId}${path}` : path); + return { - getAll: async () => { + getAll: async (spaceId?: string) => { return await measurePerformanceAsync( log, 'dataViewsApi.getAll', async (): Promise> => { const response = await kbnClient.request({ method: 'GET', - path: '/api/data_views', + path: withSpace('/api/data_views', spaceId), retries: 3, }); @@ -90,14 +104,14 @@ export const getDataViewsApiHelper = ( ); }, - get: async (id: string) => { + get: async (id: string, spaceId?: string) => { return await measurePerformanceAsync( log, 'dataViewsApi.get', async (): Promise> => { const response = await kbnClient.request({ method: 'GET', - path: `/api/data_views/data_view/${id}`, + path: withSpace(`/api/data_views/data_view/${id}`, spaceId), retries: 3, ignoreErrors: [404], }); @@ -110,14 +124,14 @@ export const getDataViewsApiHelper = ( ); }, - delete: async (id: string) => { + delete: async (id: string, spaceId?: string) => { return await measurePerformanceAsync( log, 'dataViewsApi.delete', async (): Promise => { const response = await kbnClient.request({ method: 'DELETE', - path: `/api/data_views/data_view/${id}`, + path: withSpace(`/api/data_views/data_view/${id}`, spaceId), retries: 3, ignoreErrors: [404], }); @@ -129,14 +143,14 @@ export const getDataViewsApiHelper = ( ); }, - find: async (predicate: (dataView: DataView) => boolean) => { + find: async (predicate: (dataView: DataView) => boolean, spaceId?: string) => { return await measurePerformanceAsync( log, 'dataViewsApi.find', async (): Promise> => { const response = await kbnClient.request({ method: 'GET', - path: '/api/data_views', + path: withSpace('/api/data_views', spaceId), retries: 3, }); @@ -151,14 +165,14 @@ export const getDataViewsApiHelper = ( ); }, - deleteByTitle: async (title: string) => { + deleteByTitle: async (title: string, spaceId?: string) => { return await measurePerformanceAsync( log, 'dataViewsApi.deleteByTitle', async (): Promise => { const response = await kbnClient.request({ method: 'GET', - path: '/api/data_views', + path: withSpace('/api/data_views', spaceId), retries: 3, }); @@ -172,7 +186,7 @@ export const getDataViewsApiHelper = ( const deleteResponse = await kbnClient.request({ method: 'DELETE', - path: `/api/data_views/data_view/${dataView.id}`, + path: withSpace(`/api/data_views/data_view/${dataView.id}`, spaceId), retries: 3, ignoreErrors: [404], }); @@ -183,5 +197,24 @@ export const getDataViewsApiHelper = ( } ); }, + + getIdByTitle: async (title: string, spaceId?: string) => { + return await measurePerformanceAsync(log, 'dataViewsApi.getIdByTitle', async () => { + const response = await kbnClient.request({ + method: 'GET', + path: withSpace('/api/data_views', spaceId), + retries: 3, + }); + const match = (response.data.data_view || []).find( + (dataView) => dataView.title === title || dataView.name === title + ); + if (!match?.id) { + throw new Error( + `Data view "${title}" not found${spaceId ? ` in space "${spaceId}"` : ''}` + ); + } + return match.id; + }); + }, }; }; diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/index.ts b/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/index.ts index c370ca65a531f..7840b21b978d5 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/index.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/index.ts @@ -18,6 +18,8 @@ import type { DataViewsApiService } from './data_views'; import { getDataViewsApiHelper } from './data_views'; import type { FleetApiService } from './fleet'; import { getFleetApiHelper } from './fleet'; +import type { SampleDataApiService } from './sample_data'; +import { getSampleDataApiHelper } from './sample_data'; import type { StreamsApiService } from './streams'; import { getStreamsApiService } from './streams'; @@ -26,6 +28,7 @@ export interface ApiServicesFixture { cases: CasesApiService; dataViews: DataViewsApiService; fleet: FleetApiService; + sampleData: SampleDataApiService; streams: StreamsApiService; core: CoreApiService; // add more services here @@ -45,6 +48,7 @@ export const apiServicesFixture = coreWorkerFixtures.extend< cases: getCasesApiHelper(log, kbnClient), dataViews: getDataViewsApiHelper(log, kbnClient), fleet: getFleetApiHelper(log, kbnClient), + sampleData: getSampleDataApiHelper(log, kbnClient), streams: getStreamsApiService({ kbnClient, log }), core: getCoreApiHelper(log, kbnClient), }; diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/sample_data/index.ts b/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/sample_data/index.ts new file mode 100644 index 0000000000000..4512e7baa057c --- /dev/null +++ b/src/platform/packages/shared/kbn-scout/src/playwright/fixtures/scope/worker/apis/sample_data/index.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { KbnClient, ScoutLogger } from '../../../../../../common'; +import { measurePerformanceAsync } from '../../../../../../common'; + +export interface SampleDataApiService { + install: (dataSetId: string, spaceId?: string) => Promise; + remove: (dataSetId: string, spaceId?: string) => Promise; +} + +export const getSampleDataApiHelper = ( + log: ScoutLogger, + kbnClient: KbnClient +): SampleDataApiService => { + const withSpace = (path: string, spaceId?: string) => (spaceId ? `/s/${spaceId}${path}` : path); + + return { + install: async (dataSetId: string, spaceId?: string) => { + await measurePerformanceAsync(log, 'sampleDataApi.install', async () => { + await kbnClient.request({ + method: 'POST', + path: withSpace(`/api/sample_data/${dataSetId}`, spaceId), + retries: 3, + }); + }); + }, + + remove: async (dataSetId: string, spaceId?: string) => { + await measurePerformanceAsync(log, 'sampleDataApi.remove', async () => { + await kbnClient.request({ + method: 'DELETE', + path: withSpace(`/api/sample_data/${dataSetId}`, spaceId), + retries: 3, + ignoreErrors: [404], + }); + }); + }, + }; +}; diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts index ea149124cf428..67e2817515672 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/dashboard_app.ts @@ -8,6 +8,9 @@ */ import type { ScoutPage } from '..'; +import { expect } from '..'; +import { RenderablePage } from './renderable_page'; +import { Toasts } from './toasts'; type CommonlyUsedTimeRange = | 'Today' @@ -19,49 +22,326 @@ type CommonlyUsedTimeRange = | 'Last_1 year'; export class DashboardApp { - constructor(private readonly page: ScoutPage) {} + private readonly renderable: RenderablePage; + private readonly toasts: Toasts; + // Dashboard shell and mode controls + private readonly settingsFlyout; + private readonly settingsButton; + private readonly editModeButton; + private readonly viewOnlyModeButton; + private readonly dashboardViewport; + private readonly embeddablePanel; + + // Add panel flow + private readonly addTopNavButton; + private readonly openAddPanelFlyoutButton; + private readonly panelSelectionFlyout; + private readonly panelSelectionList; + private readonly panelSelectionSearchInput; + + // Save flows + private readonly savedObjectTitleInput; + private readonly confirmSaveButton; + + // Library flyout + private readonly savedObjectsFinderTable; + private readonly savedObjectFinderLoadingIndicator; + private readonly savedObjectFinderSearchInput; + private readonly addEmbeddableSuccess; + + // Markdown panel + private readonly markdownEditorApplyButton; + private readonly markdownRenderer; + + // Customize panel flyout + private readonly customizePanelFlyout; + private readonly customizePanelSaveButton; + private readonly customizePanelCancelButton; + private readonly customizePanelTimeRangeQuickMenuButton; + + constructor(private readonly page: ScoutPage) { + this.renderable = new RenderablePage(page); + this.toasts = new Toasts(page); + + // Dashboard shell and mode controls + this.settingsFlyout = this.page.testSubj.locator('dashboardSettingsFlyout'); + this.settingsButton = this.page.testSubj.locator('dashboardSettingsButton'); + this.editModeButton = this.page.testSubj.locator('dashboardEditMode'); + this.viewOnlyModeButton = this.page.testSubj.locator('dashboardViewOnlyMode'); + this.dashboardViewport = this.page.testSubj.locator('dshDashboardViewport'); + this.embeddablePanel = this.page.testSubj.locator('embeddablePanel'); + + // Add panel flow + this.addTopNavButton = this.page.testSubj.locator('dashboardAddTopNavButton'); + this.openAddPanelFlyoutButton = this.page.testSubj.locator('dashboardOpenAddPanelFlyoutButton'); + this.panelSelectionFlyout = this.page.testSubj.locator('dashboardPanelSelectionFlyout'); + this.panelSelectionList = this.page.testSubj.locator('dashboardPanelSelectionList'); + this.panelSelectionSearchInput = this.page.testSubj.locator( + 'dashboardPanelSelectionFlyout__searchInput' + ); + + // Save flows + this.savedObjectTitleInput = this.page.testSubj.locator('savedObjectTitle'); + this.confirmSaveButton = this.page.testSubj.locator('confirmSaveSavedObjectButton'); + + // Library flyout + this.savedObjectsFinderTable = this.page.testSubj.locator('savedObjectsFinderTable'); + this.savedObjectFinderLoadingIndicator = this.page.testSubj.locator( + 'savedObjectFinderLoadingIndicator' + ); + this.savedObjectFinderSearchInput = this.page.testSubj.locator('savedObjectFinderSearchInput'); + this.addEmbeddableSuccess = this.page.testSubj.locator('addEmbeddableToDashboardSuccess'); + + // Markdown panel + this.markdownEditorApplyButton = this.page.testSubj.locator('markdownEditorApplyButton'); + this.markdownRenderer = this.page.testSubj.locator('markdownRenderer'); + + // Customize panel flyout + this.customizePanelFlyout = this.page.testSubj.locator('customizePanel'); + this.customizePanelSaveButton = this.page.testSubj.locator('saveCustomizePanelButton'); + this.customizePanelCancelButton = this.page.testSubj.locator('cancelCustomizePanelButton'); + this.customizePanelTimeRangeQuickMenuButton = this.page.testSubj.locator( + 'customizePanelTimeRangeDatePicker > superDatePickerToggleQuickMenuButton' + ); + } async goto() { await this.page.gotoApp('dashboards'); } - async waitForListingTableToLoad() { - return this.page.testSubj.waitForSelector('table-is-ready', { state: 'visible' }); + async openDashboardWithId(id: string) { + await this.page.gotoApp('dashboards', { hash: `/view/${id}` }); + await this.waitForRenderComplete(); } async openNewDashboard() { await this.page.testSubj.click('newItemButton'); - await this.page.testSubj.waitForSelector('emptyDashboardWidget', { state: 'visible' }); + } + + private getSettingsFlyout() { + return this.settingsFlyout; + } + + async openSettingsFlyout() { + // typically serverless projects + if (await this.settingsButton.isVisible()) { + await this.settingsButton.click(); + } else { + // typically stateful deployments + await this.page.getByRole('button', { name: 'Open dashboard settings' }).click(); + } + await expect(this.getSettingsFlyout()).toBeVisible(); + } + + async toggleSyncColors(value: boolean) { + const targetValue = value ? 'true' : 'false'; + const toggle = this.page.testSubj.locator('dashboardSyncColorsCheckbox'); + if ((await toggle.getAttribute('aria-checked')) !== targetValue) { + await toggle.click(); + } + } + + async applyDashboardSettings() { + await this.page.testSubj.click('applyCustomizeDashboardButton'); + await expect(this.getSettingsFlyout()).toBeHidden(); + } + + // ============================================================ + // View Mode Methods + // ============================================================ + + /** + * Checks if the dashboard is in view mode. + */ + async getIsInViewMode(): Promise { + return this.editModeButton.isVisible(); + } + + /** + * Switches the dashboard to edit mode. + */ + async switchToEditMode() { + await this.editModeButton.click(); + // Wait for edit mode to be active (drag handles appear) + // Multiple drag handles are expected when multiple panels exist. + await expect + .poll(() => this.page.testSubj.locator('embeddablePanelDragHandle').count()) + .toBeGreaterThan(0); + } + + /** + * Clicks the cancel button to exit edit mode without saving. + */ + async clickCancelOutOfEditMode() { + await expect(this.viewOnlyModeButton).toBeVisible(); + await this.viewOnlyModeButton.click(); + await expect(this.editModeButton).toBeHidden(); + } + + /** + * Opens the "Add panel" flyout for selecting panel types to add to the dashboard. + */ + async openAddPanelFlyout() { + // Click top nav add menu button and wait for menu to appear + await this.addTopNavButton.click(); + await expect(this.openAddPanelFlyoutButton).toBeVisible(); + + // Click to open the panel selection flyout and wait for it to appear + await this.openAddPanelFlyoutButton.click(); + await expect(this.panelSelectionFlyout).toBeVisible(); + await expect(this.panelSelectionList).toBeVisible(); + } + + async openNewLensPanel() { + await this.openAddPanelFlyout(); + await this.page.testSubj.click('create-action-Lens'); + await expect(this.page.testSubj.locator('lnsApp')).toBeVisible(); } async saveDashboard(name: string) { await this.page.testSubj.click('dashboardInteractiveSaveMenuItem'); - await this.page.testSubj.fill('savedObjectTitle', name); - await this.page.testSubj.click('confirmSaveSavedObjectButton'); - await this.page.testSubj.waitForSelector('confirmSaveSavedObjectButton', { state: 'hidden' }); + await this.savedObjectTitleInput.fill(name); + await this.confirmSaveButton.click(); + await expect(this.confirmSaveButton).toBeHidden(); } - async addPanelFromLibrary(...names: string[]) { - await this.page.testSubj.click('dashboardAddTopNavButton'); - await this.page.testSubj.click('dashboardAddFromLibraryButton'); - for (let i = 0; i < names.length; i++) { - // clear search input after the first panel is added - if (i > 0) { - await this.page.testSubj.clearInput('savedObjectFinderSearchInput'); - } - await this.page.testSubj.typeWithDelay('savedObjectFinderSearchInput', names[i]); - await this.page.testSubj.click(`savedObjectTitle${names[i].replace(/ /g, '-')}`); - // wait for the panel to be added - await this.page.testSubj.waitForSelector( - `embeddablePanelHeading-${names[i].replace(/[- ]/g, '')}`, - { - state: 'visible', - } - ); + async clickQuickSave() { + await expect(this.page.testSubj.locator('dashboardQuickSaveMenuItem')).toBeVisible(); + await this.page.testSubj.click('dashboardQuickSaveMenuItem'); + } + + async clearUnsavedChanges() { + const unsavedBadge = this.page.testSubj.locator('dashboardUnsavedChangesBadge'); + if (await unsavedBadge.isVisible()) { + await this.clickQuickSave(); + await expect(unsavedBadge).toBeHidden(); + await this.toasts.closeAll(); } - // close the flyout + } + + // ============================================================ + // Library Panel Methods + // ============================================================ + + /** + * Opens the "Add from library" flyout. + * Low-level building block used by addEmbeddable(). + */ + private async openLibraryFlyout() { + await this.addTopNavButton.click(); + await this.page.testSubj.click('dashboardAddFromLibraryButton'); + await expect(this.savedObjectsFinderTable).toBeVisible(); + await expect(this.savedObjectFinderLoadingIndicator).toBeHidden({ + timeout: 30_000, + }); + } + + /** + * Closes the library flyout. + */ + private async closeLibraryFlyout() { await this.page.testSubj.click('euiFlyoutCloseButton'); - await this.page.testSubj.waitForSelector('euiFlyoutCloseButton', { state: 'hidden' }); + await expect(this.page.testSubj.locator('euiFlyoutCloseButton')).toBeHidden(); + } + + /** + * Searches and filters embeddables in the library flyout. + * Uses Playwright's native type() to fire proper keyboard events. + * + * @param embeddableName - Name with dashes (e.g., 'Rendering-Test:-saved-search') + * @param embeddableType - Optional type filter (e.g., 'search', 'Visualization') + */ + private async filterEmbeddableNames(embeddableName: string, embeddableType?: string) { + // Build search query using type filter and quoted name. + // type:(search) "Rendering Test:-saved-search" (only first dash replaced with space) + const typePrefix = embeddableType ? `type:(${embeddableType}) ` : ''; + const searchQuery = `${typePrefix}"${embeddableName.replace('-', ' ')}"`; + + await this.savedObjectFinderSearchInput.click(); + await this.savedObjectFinderSearchInput.clear(); + // Use native type() which fires keydown/keyup/keypress events (not insertText) + await this.savedObjectFinderSearchInput.type(searchQuery, { delay: 50 }); + + // Wait for search results to load + await expect(this.savedObjectFinderLoadingIndicator).toBeHidden({ + timeout: 30_000, + }); + } + + /** + * Core method to add an embeddable from the library. + * + * @param embeddableName - Name with dashes (e.g., 'Rendering-Test:-saved-search') + * @param embeddableType - Optional type filter (e.g., 'search', 'Visualization') + */ + async addEmbeddable(embeddableName: string, embeddableType?: string) { + await this.openLibraryFlyout(); + await this.filterEmbeddableNames(embeddableName, embeddableType); + + // Click on the saved object title + const titleSelector = `savedObjectTitle${embeddableName.split(' ').join('-')}`; + const titleButton = this.page.testSubj.locator(titleSelector); + await expect(titleButton).toBeVisible(); + await titleButton.click(); + + // Wait for success indicator + await expect(this.addEmbeddableSuccess).toBeVisible(); + + await this.closeLibraryFlyout(); + + // Close "Added successfully" toast + await this.toasts.closeAll(); + } + + /** + * Adds a panel from the library without forcing a type filter. + */ + async addPanelFromLibrary(panelName: string, embeddableType?: string) { + return this.addEmbeddable(panelName, embeddableType); + } + + /** + * Adds a saved search to the dashboard. + * Wrapper around addEmbeddable() with type='search'. + * + * @param searchName - Name with dashes (e.g., 'Rendering-Test:-saved-search') + */ + async addSavedSearch(searchName: string) { + return this.addEmbeddable(searchName, 'search'); + } + + /** + * Adds a Lens visualization to the dashboard from the library. + * Wrapper around addEmbeddable() with type='lens'. + * + * @param lensName - Name of the Lens saved object + */ + async addLens(lensName: string) { + return this.addEmbeddable(lensName, 'lens'); + } + + /** + * Adds a new Markdown panel (by value) to the dashboard. + * + * @param content - Markdown content to save + */ + async addMarkdownPanel(content: string) { + await this.openAddPanelFlyout(); + await this.panelSelectionSearchInput.fill('Markdown text'); + await this.page.testSubj.click('create-action-Markdown text'); + + const editorInput = this.page.locator('textarea[aria-label="Dashboard markdown editor"]'); + await expect(editorInput).toBeVisible(); + await editorInput.fill(content); + await this.markdownEditorApplyButton.click(); + + await expect(this.markdownRenderer).toBeVisible(); + } + + async addMapPanel() { + await this.openAddPanelFlyout(); + await this.panelSelectionSearchInput.fill('Maps'); + await this.page.testSubj.click('create-action-Maps'); } async customizePanel(options: { @@ -74,16 +354,14 @@ export class DashboardApp { await this.page.testSubj.click('embeddablePanelAction-ACTION_CUSTOMIZE_PANEL'); if (options.customTimeRageCommonlyUsed) { await this.page.testSubj.click('customizePanelShowCustomTimeRange'); - await this.page.testSubj.click( - 'customizePanelTimeRangeDatePicker > superDatePickerToggleQuickMenuButton' - ); + await this.customizePanelTimeRangeQuickMenuButton.click(); await this.page.testSubj.click( `superDatePickerCommonlyUsed_${options.customTimeRageCommonlyUsed.value}` ); } - await this.page.testSubj.click('saveCustomizePanelButton'); - await this.page.testSubj.waitForSelector('saveCustomizePanelButton', { state: 'hidden' }); + await this.customizePanelSaveButton.click(); + await expect(this.customizePanelSaveButton).toBeHidden(); } async removePanel(name: string | 'embeddableError') { @@ -93,9 +371,7 @@ export class DashboardApp { await this.page.testSubj.locator(panelHeaderTestSubj).hover(); await this.page.testSubj.click('embeddablePanelToggleMenuIcon'); await this.page.testSubj.click('embeddablePanelAction-deletePanel'); - await this.page.testSubj.waitForSelector(panelHeaderTestSubj, { - state: 'hidden', - }); + await expect(this.page.testSubj.locator(panelHeaderTestSubj)).toBeHidden(); } async waitForPanelsToLoad( @@ -105,16 +381,475 @@ export class DashboardApp { selector: '[data-test-subj="embeddablePanel"][data-render-complete="true"]', } ) { - const startTime = Date.now(); + await expect + .poll(() => this.page.locator(options.selector).count(), { + timeout: options.timeout, + intervals: [100], + }) + .toBe(expectedCount); + } - while (Date.now() - startTime < options.timeout) { - const count = await this.page.locator(options.selector).count(); - if (count === expectedCount) return; - // Short polling interval - // eslint-disable-next-line playwright/no-wait-for-timeout - await this.page.waitForTimeout(100); + /** + * Gets the titles of all panels on the dashboard + */ + async getPanelTitles(): Promise { + const titleElements = await this.page.testSubj.locator('embeddablePanelTitle').all(); + return Promise.all(titleElements.map(async (el) => (await el.textContent()) ?? '')); + } + + getPanelTitlesLocator() { + return this.page.testSubj.locator('embeddablePanelTitle'); + } + + /** + * Gets the count of panels on the dashboard + */ + async getPanelCount(): Promise { + return this.embeddablePanel.count(); + } + + /** + * Gets the count of dashboard controls + */ + async getControlCount(): Promise { + return this.page.testSubj.locator('control-frame').count(); + } + + async getSavedSearchRowCount(): Promise { + return this.page.evaluate(() => { + const docElement = document.querySelector('[data-document-number]'); + const docCount = Number(docElement?.getAttribute('data-document-number') ?? '0'); + const rowCount = document.querySelectorAll( + '[data-test-subj="docTableExpandToggleColumn"]' + ).length; + return Math.max(docCount, rowCount); + }); + } + + async getTagCloudTexts(): Promise { + const tagClouds = this.page.testSubj.locator('tagCloudVisualization'); + const clouds = await tagClouds.all(); + return Promise.all( + clouds.map(async (tagCloud) => tagCloud.locator('.echLegendItemLabel').allInnerTexts()) + ); + } + + async getSharedItemsCount(): Promise { + const attributeName = 'data-shared-items-count'; + const elements = await this.page.locator(`[${attributeName}]`).all(); + if (elements.length === 0) { + throw new Error(`no element`); } + const attribute = await elements[0].getAttribute(attributeName); + if (!attribute) { + throw new Error(`no attribute found for [${attributeName}]`); + } + return Number(attribute); + } + + async getPanelGroupOrder(): Promise { + const panelGroups = await this.panelSelectionList + .locator('[data-test-subj*="dashboardEditorMenu-"]') + .all(); + + const panelGroupData = await Promise.all( + panelGroups.map(async (panelGroup) => { + const order = await panelGroup.getAttribute('data-group-sort-order'); + const testSubj = await panelGroup.getAttribute('data-test-subj'); + const match = testSubj?.match(/dashboardEditorMenu-(.*)/); + return { order, groupTitle: match?.[1] }; + }) + ); + + const panelGroupByOrder = new Map(); + panelGroupData + .filter((item): item is { order: string; groupTitle: string } => + Boolean(item.order && item.groupTitle) + ) + .forEach((item) => panelGroupByOrder.set(item.order, item.groupTitle)); + + return [...panelGroupByOrder.values()]; + } + + async getPanelTypeCount(): Promise { + return this.panelSelectionList.locator('li').count(); + } + + /** + * Waits for all dashboard panels to finish rendering. + * Uses the data-render-complete attribute to determine completion. + */ + async waitForRenderComplete() { + await expect(this.dashboardViewport).toBeVisible(); + + try { + const count = await this.getSharedItemsCount(); + if (count > 0) { + await this.renderable.waitForRender(count); + return; + } + } catch { + // fall back to embeddable panel count + } + + const count = await this.embeddablePanel.count(); + if (count > 0) { + await this.waitForPanelsToLoad(count); + } + } + + // ============================================================ + // Customize Panel Methods + // ============================================================ + + private readonly customTimeRangeToggleTestSubj = 'customizePanelShowCustomTimeRange'; + + private async waitForCustomTimeRangeToggleState(enabled: boolean) { + const expected = enabled ? 'true' : 'false'; + const selector = `[data-test-subj="${this.customTimeRangeToggleTestSubj}"]`; + await this.page.waitForFunction( + ({ selector: selectorArg, expectedValue }) => + document.querySelector(selectorArg)?.getAttribute('aria-checked') === expectedValue, + { selector, expectedValue: expected } + ); + } + + private getCustomizePanelFlyout() { + return this.customizePanelFlyout; + } + + async enableCustomTimeRange() { + const toggle = this.getCustomizePanelFlyout().locator( + `[data-test-subj="${this.customTimeRangeToggleTestSubj}"]` + ); + if ((await toggle.getAttribute('aria-checked')) !== 'true') { + await toggle.click(); + } + await this.waitForCustomTimeRangeToggleState(true); + await this.getCustomizePanelFlyout() + .locator('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .waitFor({ state: 'visible' }); + } + + async disableCustomTimeRange() { + const toggle = this.getCustomizePanelFlyout().locator( + `[data-test-subj="${this.customTimeRangeToggleTestSubj}"]` + ); + if ((await toggle.getAttribute('aria-checked')) !== 'false') { + await toggle.click(); + } + await this.waitForCustomTimeRangeToggleState(false); + } + + async openDatePickerQuickMenu() { + await this.getCustomizePanelFlyout() + .locator('[data-test-subj="superDatePickerToggleQuickMenuButton"]') + .click(); + } + + async clickCommonlyUsedTimeRange(timeRange: CommonlyUsedTimeRange) { + await this.page.testSubj.click(`superDatePickerCommonlyUsed_${timeRange}`); + } + + async openCustomizePanel(title?: string) { + await this.clickPanelAction('embeddablePanelAction-ACTION_CUSTOMIZE_PANEL', title); + await expect(this.customizePanelFlyout).toBeVisible(); + } + + async closeCustomizePanel() { + await this.customizePanelCancelButton.click(); + await expect(this.customizePanelFlyout).toBeHidden(); + } + + async getCustomPanelTitle() { + return this.page.testSubj.locator('customEmbeddablePanelTitleInput').inputValue(); + } + + async setCustomPanelTitle(customTitle: string) { + const titleInput = this.page.testSubj.locator('customEmbeddablePanelTitleInput'); + await titleInput.click(); + await titleInput.fill(customTitle); + } + + async resetCustomPanelTitle() { + await this.page.testSubj.click('resetCustomEmbeddablePanelTitleButton'); + } + + getResetCustomPanelTitleButton() { + return this.page.testSubj.locator('resetCustomEmbeddablePanelTitleButton'); + } + + async getCustomPanelDescription() { + return this.page.testSubj.locator('customEmbeddablePanelDescriptionInput').inputValue(); + } + + async setCustomPanelDescription(customDescription: string) { + const descriptionInput = this.page.testSubj.locator('customEmbeddablePanelDescriptionInput'); + await descriptionInput.click(); + await descriptionInput.fill(customDescription); + } + + async resetCustomPanelDescription() { + await this.page.testSubj.click('resetCustomEmbeddablePanelDescriptionButton'); + } + + getResetCustomPanelDescriptionButton() { + return this.page.testSubj.locator('resetCustomEmbeddablePanelDescriptionButton'); + } + + async saveCustomizePanel() { + await this.customizePanelSaveButton.click(); + await expect(this.customizePanelFlyout).toBeHidden(); + } + + async expectTimeRangeBadgeExists() { + await expect( + this.page.testSubj.locator('embeddablePanelBadge-CUSTOM_TIME_RANGE_BADGE') + ).toBeVisible(); + } + + async expectTimeRangeBadgeMissing() { + await expect( + this.page.testSubj.locator('embeddablePanelBadge-CUSTOM_TIME_RANGE_BADGE') + ).toBeHidden(); + } + + async expectEmptyPlaceholderVisible() { + await expect(this.page.testSubj.locator('emptyPlaceholder')).toBeVisible(); + } + + async expectXYVisChartVisible() { + await expect(this.page.testSubj.locator('xyVisChart')).toBeVisible(); + } + + async clickTimeRangeBadge() { + await this.page.testSubj.click('embeddablePanelBadge-CUSTOM_TIME_RANGE_BADGE'); + } + + // ============================================================ + // Panel Action Methods + // ============================================================ + + /** + * Formats a panel title for use in test subject selectors. + */ + private formatTitleForTestSubj(title: string): string { + return title.replace(/\s/g, ''); + } + + /** + * Gets the hover actions wrapper for a panel by title. + * + * @param title - Panel title. If empty, finds first panel by class. + */ + getPanelHoverActionsLocator(title?: string) { + if (!title) { + // Fallback: find first panel by class when no title provided + return this.page.locator('.embPanel__hoverActionsAnchor'); + } + return this.page.testSubj.locator( + `embeddablePanelHoverActions-${this.formatTitleForTestSubj(title)}` + ); + } + + /** + * Opens the context menu for a panel. + * Scrolls the panel into view and clicks the menu toggle. + */ + async openPanelContextMenu(title?: string) { + const panelWrapper = this.getPanelHoverActionsLocator(title); + await panelWrapper.scrollIntoViewIfNeeded(); + await panelWrapper.hover(); + + const contextMenuOpen = panelWrapper.locator( + '[data-test-subj="embeddablePanelContextMenuOpen"]' + ); + if (await contextMenuOpen.isVisible()) { + return; + } + + const menuIcon = panelWrapper.locator('[data-test-subj="embeddablePanelToggleMenuIcon"]'); + await menuIcon.click(); + await expect(contextMenuOpen).toBeVisible(); + } + + async navigateToLensEditorFromPanel(title?: string) { + await this.openPanelContextMenu(title); + await this.page.testSubj.click('embeddablePanelAction-editPanel'); + const navigateToLensEditorLink = this.page.testSubj.locator('navigateToLensEditorLink'); + await expect(navigateToLensEditorLink).toBeVisible(); + await navigateToLensEditorLink.click(); + await expect(this.page.testSubj.locator('lnsApp')).toBeVisible(); + } + + /** + * Checks if a panel action exists as a descendant of the panel wrapper. + */ + private async panelActionExistsInWrapper( + actionTestSubj: string, + panelWrapper: ReturnType + ): Promise { + // Check if the action is a descendant of the panel wrapper (not globally visible) + const actionInPanel = panelWrapper.locator(`[data-test-subj="${actionTestSubj}"]`); + return actionInPanel.isVisible(); + } + + /** + * Clicks a panel action from the hover actions or context menu. + * + * Key difference from before: checks if action is DESCENDANT of panel wrapper, + * not just globally visible. This prevents clicking wrong panel's actions. + */ + async clickPanelAction(actionTestSubj: string, title?: string) { + const panelWrapper = this.getPanelHoverActionsLocator(title); + await panelWrapper.scrollIntoViewIfNeeded(); + await panelWrapper.hover(); + + // Check if action exists as descendant of panel wrapper + const existsInWrapper = await this.panelActionExistsInWrapper(actionTestSubj, panelWrapper); + + if (existsInWrapper) { + // Click the action within the panel wrapper + const actionInPanel = panelWrapper.locator(`[data-test-subj="${actionTestSubj}"]`); + await actionInPanel.click(); + } else { + // Open context menu and click action + await this.openPanelContextMenu(title); + await this.page.testSubj.click(actionTestSubj); + // Wait for context menu to close after clicking the action + await expect(this.page.testSubj.locator('embeddablePanelContextMenuOpen')).toBeHidden(); + } + } + + /** + * Clones a panel on the dashboard. + * The cloned panel becomes a "by value" panel (not linked to library). + */ + async clonePanel(title?: string) { + const panels = this.page.testSubj.locator('embeddablePanel'); + const initialCount = await panels.count(); + + await this.clickPanelAction('embeddablePanelAction-clonePanel', title); + await this.page.waitForFunction( + (expectedCount) => + document.querySelectorAll('[data-test-subj="embeddablePanel"]').length > expectedCount, + initialCount + ); + } + + /** + * Unlinks a panel from the library, converting it to a "by value" panel. + * Unlinks a panel from the library and verifies it is unlinked. + */ + async unlinkFromLibrary(title?: string) { + await this.clickPanelAction('embeddablePanelAction-unlinkFromLibrary', title); + await expect(this.page.testSubj.locator('unlinkPanelSuccess')).toBeVisible(); + // Verify the panel is now unlinked + await this.expectNotLinkedToLibrary(title); + } + + /** + * Saves a panel to the library with a new title. + * Saves a panel to the library and verifies it is linked. + */ + async saveToLibrary(newTitle: string, currentTitle?: string) { + await this.clickPanelAction('embeddablePanelAction-saveToLibrary', currentTitle); + + // Fill in the new title + await this.savedObjectTitleInput.fill(newTitle); + await this.confirmSaveButton.click(); + + // Wait for success + await expect(this.page.testSubj.locator('addPanelToLibrarySuccess')).toBeVisible(); + // Verify the panel is now linked + await this.expectLinkedToLibrary(newTitle); + } + + /** + * Checks if a panel has a specific action available. + * Checks both hover actions and context menu. + * + * Uses count() > 0 to include hidden elements. + * (finds elements even if not visible). + */ + async panelHasAction(actionTestSubj: string, title?: string): Promise { + const panelWrapper = this.getPanelHoverActionsLocator(title); + await panelWrapper.scrollIntoViewIfNeeded(); + await panelWrapper.hover(); + + // Check if action exists in panel wrapper (hover actions) + // Using count() to include hidden elements + const existsInWrapper = await this.panelActionExistsInWrapper(actionTestSubj, panelWrapper); + if (existsInWrapper) { + return true; + } + + // Check in context menu + await this.openPanelContextMenu(title); + // Use count() > 0 to find elements even if hidden + const actionInMenu = this.page.testSubj.locator(actionTestSubj); + const count = await actionInMenu.count(); + + // Close menu by pressing Escape + await this.page.keyboard.press('Escape'); + return count > 0; + } + + /** + * Asserts that a panel action exists (throws if not found). + */ + async expectExistsPanelAction(actionTestSubj: string, title?: string) { + const exists = await this.panelHasAction(actionTestSubj, title); + if (!exists) { + // Collect available actions for better error message + await this.openPanelContextMenu(title); + const allActions = await this.page + .locator('[data-test-subj^="embeddablePanelAction-"]') + .all(); + const actionNames: string[] = []; + for (const action of allActions) { + const testSubj = await action.getAttribute('data-test-subj'); + if (testSubj) actionNames.push(testSubj); + } + await this.page.keyboard.press('Escape'); + + throw new Error( + `Expected panel action "${actionTestSubj}" to exist for panel "${ + title || 'first panel' + }". Available actions: [${actionNames.join(', ')}]` + ); + } + } + + /** + * Verifies a panel is linked to the library. + * A linked panel has the "Unlink from library" action available. + * + * Switches to edit mode before checking because library actions + * may not be available in view mode. + */ + async expectLinkedToLibrary(title?: string) { + await this.expectExistsPanelAction('embeddablePanelAction-unlinkFromLibrary', title); + } + + /** + * Verifies a panel is NOT linked to the library. + * A non-linked panel has the "Save to library" action available. + * + * Switches to edit mode before checking because library actions + * may not be available in view mode. + */ + async expectNotLinkedToLibrary(title?: string) { + await this.expectExistsPanelAction('embeddablePanelAction-saveToLibrary', title); + } + + async openInlineEditor(id: string) { + // Hover over the panel to show action buttons + const embeddableSelector = `[data-test-embeddable-id="${id}"]`; + await this.page.locator(embeddableSelector).hover(); - throw new Error(`Timeout waiting for ${expectedCount} elements matching ${options.selector}`); + // Wait for the edit button to appear and click it + const editVisualizationConfigurationSelector = `[data-test-subj="hover-actions-${id}"] [data-test-subj="embeddablePanelAction-editPanel"]`; + await this.page.locator(editVisualizationConfigurationSelector).click(); } } diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/date_picker.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/date_picker.ts index 6219d6feb5681..6ee3ead0913ee 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/date_picker.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/date_picker.ts @@ -18,7 +18,19 @@ export enum DateUnitSelector { } export class DatePicker { - constructor(private readonly page: ScoutPage) {} + private readonly quickMenuButton; + private readonly toggleRefreshButton; + private readonly refreshIntervalInput; + private readonly refreshIntervalUnitSelect; + + constructor(private readonly page: ScoutPage) { + this.quickMenuButton = this.page.testSubj.locator('superDatePickerToggleQuickMenuButton'); + this.toggleRefreshButton = this.page.testSubj.locator('superDatePickerToggleRefreshButton'); + this.refreshIntervalInput = this.page.testSubj.locator('superDatePickerRefreshIntervalInput'); + this.refreshIntervalUnitSelect = this.page.testSubj.locator( + 'superDatePickerRefreshIntervalUnitsSelect' + ); + } private async showStartEndTimes(containerLocator?: Locator) { const getTestSubjLocator = (selector: string) => @@ -117,6 +129,13 @@ export class DatePicker { await getTestSubjLocator('querySubmitButton').click(); } + async setCommonlyUsedTime(option: string) { + await this.quickMenuButton.click(); + const commonlyUsedOption = this.page.testSubj.locator(`superDatePickerCommonlyUsed_${option}`); + await expect(commonlyUsedOption).toBeVisible(); + await commonlyUsedOption.click(); + } + async setAbsoluteRange({ from, to }: { from: string; to: string }) { await this.showStartEndTimes(); await this.typeAbsoluteRange({ from, to, validateDates: true }); @@ -152,21 +171,18 @@ export class DatePicker { } async startAutoRefresh(interval: number, dateUnit: DateUnitSelector = DateUnitSelector.Seconds) { - await this.page.testSubj.click('superDatePickerToggleQuickMenuButton'); + await this.quickMenuButton.click(); // Check if refresh is already running - const toggleButton = this.page.testSubj.locator('superDatePickerToggleRefreshButton'); - const isPaused = (await toggleButton.getAttribute('aria-checked')) === 'false'; + const isPaused = (await this.toggleRefreshButton.getAttribute('aria-checked')) === 'false'; if (isPaused) { - await toggleButton.click(); + await this.toggleRefreshButton.click(); } // Set interval - const intervalInput = this.page.testSubj.locator('superDatePickerRefreshIntervalInput'); - await intervalInput.clear(); - await intervalInput.fill(interval.toString()); - const timeUnit = this.page.testSubj.locator('superDatePickerRefreshIntervalUnitsSelect'); - await timeUnit.selectOption({ value: dateUnit }); - await intervalInput.press('Enter'); - - await this.page.testSubj.click('superDatePickerToggleQuickMenuButton'); + await this.refreshIntervalInput.clear(); + await this.refreshIntervalInput.fill(interval.toString()); + await this.refreshIntervalUnitSelect.selectOption({ value: dateUnit }); + await this.refreshIntervalInput.press('Enter'); + + await this.quickMenuButton.click(); } } diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/discover_app.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/discover_app.ts index b558014988d39..8c7b623e4b76c 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/discover_app.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/discover_app.ts @@ -17,23 +17,58 @@ export class DiscoverApp { async goto() { await this.page.gotoApp('discover'); + await this.waitForDataViewSwitch(); + } + + private async getVisibleDataViewSwitch() { + const discoverSwitch = this.page.testSubj.locator('discover-dataView-switch-link'); + const fallbackSwitch = this.page.testSubj.locator('dataView-switch-link'); + + // There should be exactly one visible data view switch. + // If both are visible (bug), fail explicitly instead of picking one + await expect(discoverSwitch.or(fallbackSwitch)).toBeVisible(); + + const discoverVisible = await discoverSwitch.isVisible(); + const fallbackVisible = await fallbackSwitch.isVisible(); + + if (discoverVisible === fallbackVisible) { + throw new Error( + `Expected exactly one data view switch link to be visible, but discover=${discoverVisible} fallback=${fallbackVisible}` + ); + } + + return discoverVisible ? discoverSwitch : fallbackSwitch; + } + + private async waitForDataViewSwitch() { + await this.getVisibleDataViewSwitch(); } async selectDataView(name: string) { - const currentValue = await this.page.testSubj.innerText('*dataView-switch-link'); + const dataViewSwitch = await this.getVisibleDataViewSwitch(); + const currentValue = await dataViewSwitch.innerText(); if (currentValue === name) { return; } - await this.page.testSubj.click('*dataView-switch-link'); - await this.page.testSubj.waitForSelector('indexPattern-switcher'); + await dataViewSwitch.click(); + await expect(this.page.testSubj.locator('indexPattern-switcher')).toBeVisible(); await this.page.testSubj.typeWithDelay('indexPattern-switcher--input', name); - await this.page.testSubj.locator('indexPattern-switcher').locator(`[title="${name}"]`).click(); - await this.page.testSubj.waitForSelector('indexPattern-switcher', { state: 'hidden' }); + const matchingDataViewLocator = this.page.testSubj + .locator('indexPattern-switcher') + .locator(`[title="${name}"]`); + if (await matchingDataViewLocator.isVisible()) { + await matchingDataViewLocator.click(); + } else { + await this.page.testSubj.locator('explore-matching-indices-button').click(); + } + await expect(this.page.testSubj.locator('indexPattern-switcher')).toBeHidden(); await this.waitUntilFieldListHasCountOfFields(); } getSelectedDataView(): Locator { - return this.page.testSubj.locator('discover-dataView-switch-link'); + return this.page.testSubj + .locator('discover-dataView-switch-link') + .or(this.page.testSubj.locator('dataView-switch-link')); } async clickNewSearch() { @@ -48,7 +83,6 @@ export class DiscoverApp { await this.page.testSubj.fill('savedObjectTitle', name); await this.page.testSubj.click('confirmSaveSavedObjectButton'); await this.page.testSubj.waitForSelector('savedObjectSaveModal', { state: 'hidden' }); - await this.page.waitForLoadingIndicatorHidden(); } async waitUntilFieldListHasCountOfFields() { @@ -157,17 +191,6 @@ export class DiscoverApp { return headers.join(','); } - async getSharedItemTitleAndDescription(): Promise<{ title: string; description: string }> { - const cssSelector = '[data-shared-item][data-title][data-description]'; - const element = this.page.locator(cssSelector); - await element.waitFor({ state: 'visible' }); - - const title = (await element.getAttribute('data-title')) || ''; - const description = (await element.getAttribute('data-description')) || ''; - - return { title, description }; - } - async showChart() { await this.page.testSubj.click('dscShowHistogramButton'); } diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/fiter_bar.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/filter_bar.ts similarity index 93% rename from src/platform/packages/shared/kbn-scout/src/playwright/page_objects/fiter_bar.ts rename to src/platform/packages/shared/kbn-scout/src/playwright/page_objects/filter_bar.ts index 4523251cd8fbd..45d4bf638c695 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/fiter_bar.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/filter_bar.ts @@ -52,8 +52,12 @@ export class FilterBar { await expect(filterParamsInput).toBeEditable(); await filterParamsInput.focus(); await this.page.typeWithDelay('[data-test-subj="filterParams"] input', options.value); - // save filter + // save filter and wait for popover to close await this.page.testSubj.click('saveFilter'); + await expect( + this.page.testSubj.locator('addFilterPopover'), + 'Filter popover should close after saving' + ).toBeHidden(); await expect( this.page.testSubj.locator('^filter-badge'), diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/index.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/index.ts index 21434371d9e79..6ce247e67ee9f 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/index.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/index.ts @@ -14,12 +14,13 @@ import { CollapsibleNav } from './collapsible_nav'; import { DashboardApp } from './dashboard_app'; import { DatePicker } from './date_picker'; import { DiscoverApp } from './discover_app'; -import { FilterBar } from './fiter_bar'; +import { FilterBar } from './filter_bar'; import { MapsPage } from './maps_page'; import { RenderablePage } from './renderable_page'; import { Toasts } from './toasts'; import { createLazyPageObject } from './utils'; import { Inspector } from './inspector'; +import { LensApp } from './lens_app'; export interface PageObjectsFixtures { page: ScoutPage; @@ -37,6 +38,7 @@ export interface PageObjects { collapsibleNav: CollapsibleNav; toasts: Toasts; inspector: Inspector; + lens: LensApp; } /** @@ -56,6 +58,7 @@ export function createCorePageObjects(fixtures: PageObjectsFixtures): PageObject collapsibleNav: createLazyPageObject(CollapsibleNav, fixtures.page, fixtures.config), toasts: createLazyPageObject(Toasts, fixtures.page), inspector: createLazyPageObject(Inspector, fixtures.page), + lens: createLazyPageObject(LensApp, fixtures.page), // Add new page objects here }; } diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/lens_app.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/lens_app.ts new file mode 100644 index 0000000000000..0f7a14e19dbb0 --- /dev/null +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/lens_app.ts @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { ScoutPage } from '..'; +import { EuiComboBoxWrapper, expect } from '..'; + +export class LensApp { + private readonly lensApp; + private readonly chartSwitchPopover; + private readonly chartSwitchList; + private readonly saveAndReturnButton; + private readonly closeDimensionEditorButton; + public readonly applyChangesButton; + private readonly dimensionFieldComboBox; + + constructor(private readonly page: ScoutPage) { + this.lensApp = this.page.testSubj.locator('lnsApp'); + this.chartSwitchPopover = this.page.testSubj.locator('lnsChartSwitchPopover'); + this.chartSwitchList = this.page.testSubj.locator('lnsChartSwitchList'); + this.saveAndReturnButton = this.page.testSubj.locator('lnsApp_saveAndReturnButton'); + this.closeDimensionEditorButton = this.page.testSubj.locator( + 'lns-indexPattern-dimensionContainerClose' + ); + this.applyChangesButton = this.page.testSubj.locator('lnsApplyChanges__apply'); + this.dimensionFieldComboBox = new EuiComboBoxWrapper(this.page, 'indexPattern-dimension-field'); + } + + async waitForLensApp() { + await expect(this.lensApp).toBeVisible(); + } + + async switchToVisualization(visType: string) { + await this.openChartSwitchPopover(); + await this.page.testSubj.locator(`lnsChartSwitchPopover_${visType}`).click(); + } + + async applyChanges() { + await this.applyChangesButton.click(); + await expect(this.applyChangesButton).toBeHidden(); + } + + /** + * Clicks "Save and return" and waits for Lens to close and the dashboard + * viewport to be visible. + */ + async saveAndReturn() { + await expect(this.saveAndReturnButton).toBeVisible(); + await this.saveAndReturnButton.click(); + await expect(this.lensApp).toBeHidden(); + await expect(this.page.testSubj.locator('dshDashboardViewport')).toBeVisible(); + } + + async configureXYDimensions(options?: { + y?: { operation: string; field?: string }; + x?: { operation: string; field?: string }; + split?: { + operation: string; + field?: string; + palette?: { mode: 'legacy' | 'colorMapping'; id: string }; + }; + }) { + const y = options?.y ?? { operation: 'average', field: 'bytes' }; + const x = options?.x ?? { operation: 'date_histogram', field: '@timestamp' }; + const split = options?.split ?? { operation: 'terms', field: 'ip' }; + + await this.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: y.operation, + field: y.field, + }); + await this.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: x.operation, + field: x.field, + }); + await this.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: split.operation, + field: split.field, + palette: split.palette, + }); + } + + async configureDimension(opts: { + dimension: string; + operation: string; + field?: string; + palette?: { mode: 'legacy' | 'colorMapping'; id: string }; + keepOpen?: boolean; + }) { + await this.openDimensionEditor(opts.dimension); + await this.selectOperation(opts.operation); + if (opts.field) { + await this.selectField(opts.field); + } + if (opts.palette) { + await this.setPalette(opts.palette.id, opts.palette.mode === 'legacy'); + } + if (!opts.keepOpen) { + await this.closeDimensionEditor(); + } + } + + async openDimensionEditorFor(dimension: string) { + await this.openDimensionEditor(dimension); + } + + async closeDimensionEditorPanel() { + await this.closeDimensionEditor(); + } + + async setTermsNumberOfValues(value: number) { + const input = this.page.locator('input[data-test-subj="indexPattern-terms-values"]'); + await expect(input).toBeVisible(); + await input.click(); + await input.fill(`${value}`); + await this.page.keyboard.press('Tab'); + await expect(input).toHaveValue(`${value}`); + } + + async setTableDynamicColoring(coloringType: 'none' | 'cell' | 'text') { + await this.page.testSubj.click(`lnsDatatable_dynamicColoring_groups_${coloringType}`); + } + + async setPalette(paletteId: string, isLegacy: boolean) { + await this.page.testSubj.click('lns_colorEditing_trigger'); + await expect(this.page.testSubj.locator('lns-palettePanelFlyout')).toBeVisible(); + + const paletteModeToggle = this.page.testSubj.locator('lns_colorMappingOrLegacyPalette_switch'); + const targetValue = isLegacy ? 'true' : 'false'; + if ((await paletteModeToggle.getAttribute('aria-checked')) !== targetValue) { + await paletteModeToggle.click(); + } + + if (isLegacy) { + await this.page.testSubj.click('lns-palettePicker'); + await this.page.locator(`#${paletteId}`).click(); + } else { + await this.page.testSubj.click('kbnColoring_ColorMapping_PalettePicker'); + await this.page.testSubj.click(`kbnColoring_ColorMapping_Palette-${paletteId}`); + } + + await this.closePaletteEditor(); + } + + private async closePaletteEditor() { + await this.page.testSubj.click('lns-indexPattern-SettingWithSiblingFlyoutBack'); + await expect( + this.page.testSubj.locator('lns-indexPattern-SettingWithSiblingFlyoutBack') + ).toBeHidden(); + } + + private async openDimensionEditor(dimension: string) { + await this.page.testSubj.locator(dimension).click(); + await expect(this.closeDimensionEditorButton).toBeVisible(); + } + + private async closeDimensionEditor() { + await this.closeDimensionEditorButton.click(); + await expect(this.closeDimensionEditorButton).toBeHidden(); + } + + private async selectOperation(operation: string, isPreviousIncompatible = false) { + const operationSelector = isPreviousIncompatible + ? `lns-indexPatternDimension-${operation} incompatible` + : `lns-indexPatternDimension-${operation}`; + const operationButton = this.page.testSubj.locator(operationSelector); + await expect(operationButton).toBeVisible(); + await operationButton.scrollIntoViewIfNeeded(); + await operationButton.click(); + await expect(operationButton).toHaveAttribute('aria-pressed', 'true'); + } + + private async selectField(field: string) { + await this.dimensionFieldComboBox.selectSingleOption(field, { + optionTestSubj: `lns-fieldOption-${field}`, + }); + } + + private async openChartSwitchPopover() { + await this.chartSwitchPopover.click(); + await expect(this.chartSwitchList).toBeVisible(); + } + + getConvertToEsqlButton() { + return this.page.getByRole('button', { name: 'Convert to ES|QL' }); + } + + getConvertToEsqModal() { + return this.page.getByTestId('lnsConvertToEsqlModal'); + } + + getConvertToEsqModalConfirmButton() { + return this.page.getByTestId('confirmModalConfirmButton'); + } +} diff --git a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/maps_page.ts b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/maps_page.ts index 99ea4eda3e5ce..a14b7cb46e1c7 100644 --- a/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/maps_page.ts +++ b/src/platform/packages/shared/kbn-scout/src/playwright/page_objects/maps_page.ts @@ -13,18 +13,71 @@ import type { ScoutPage } from '..'; const DEFAULT_MAP_LOADING_TIMEOUT = 20_000; export class MapsPage { - constructor(private readonly page: ScoutPage) {} + public readonly mapContainer; + public readonly mapRenderComplete; + public readonly saveAndReturnButton; + public readonly saveButton; + public readonly addLayerButton; + public readonly layerAddForm; + public readonly importFileButton; + public readonly savedObjectTitleInput; + public readonly returnToOriginSwitch; + public readonly confirmSaveButton; + + constructor(private readonly page: ScoutPage) { + this.mapContainer = this.page.locator('#maps-plugin'); + this.mapRenderComplete = this.mapContainer.locator( + 'div[data-dom-id][data-render-complete="true"]' + ); + this.saveAndReturnButton = this.page.testSubj.locator('mapSaveAndReturnButton'); + this.saveButton = this.page.testSubj.locator('mapSaveButton'); + this.addLayerButton = this.page.testSubj.locator('addLayerButton'); + this.layerAddForm = this.page.testSubj.locator('layerAddForm'); + this.importFileButton = this.page.testSubj.locator('importFileButton'); + this.savedObjectTitleInput = this.page.testSubj.locator('savedObjectTitle'); + this.returnToOriginSwitch = this.page.testSubj.locator('returnToOriginModeSwitch'); + this.confirmSaveButton = this.page.testSubj.locator('confirmSaveSavedObjectButton'); + } async gotoNewMap() { - return this.page.gotoApp('maps/map'); + await this.page.gotoApp('maps/map'); + await this.waitForRenderComplete(); } async waitForRenderComplete() { // first wait for the top level container to be present - await this.page.locator('div#maps-plugin').waitFor({ timeout: DEFAULT_MAP_LOADING_TIMEOUT }); + await this.mapContainer.waitFor({ state: 'visible', timeout: DEFAULT_MAP_LOADING_TIMEOUT }); // then wait for the map to be fully rendered - return this.page - .locator('div[data-dom-id][data-render-complete="true"]') - .waitFor({ timeout: DEFAULT_MAP_LOADING_TIMEOUT }); + return this.mapRenderComplete.waitFor({ + state: 'attached', + timeout: DEFAULT_MAP_LOADING_TIMEOUT, + }); + } + + async selectLayerWizardByTitle(title: string) { + const wizardTestSubj = title + .split(' ') + .map((segment, index) => + index === 0 ? segment.toLowerCase() : segment.charAt(0).toUpperCase() + segment.slice(1) + ) + .join(''); + await this.page.testSubj.click(wizardTestSubj); + } + + async saveFromModal(title: string, { redirectToOrigin = true }: { redirectToOrigin?: boolean }) { + await this.savedObjectTitleInput.fill(title); + if (await this.returnToOriginSwitch.isVisible()) { + const isChecked = (await this.returnToOriginSwitch.getAttribute('aria-checked')) === 'true'; + if (isChecked !== redirectToOrigin) { + await this.returnToOriginSwitch.click(); + } + } + await this.confirmSaveButton.click(); + await this.confirmSaveButton.waitFor({ state: 'hidden' }); + } + + getLayerToggleButton(displayName: string) { + const escapedName = displayName.replace(/\s+/g, '_'); + return this.page.testSubj.locator(`layerTocActionsPanelToggleButton${escapedName}`); } } diff --git a/src/platform/plugins/shared/dashboard/moon.yml b/src/platform/plugins/shared/dashboard/moon.yml index 13357009cce5d..9238b0c19227a 100644 --- a/src/platform/plugins/shared/dashboard/moon.yml +++ b/src/platform/plugins/shared/dashboard/moon.yml @@ -106,6 +106,8 @@ dependsOn: - '@kbn/presentation-util' - '@kbn/background-search' - '@kbn/react-query' + - '@kbn/scout' + - '@kbn/test' tags: - plugin - prod @@ -119,6 +121,7 @@ fileGroups: - common/**/* - public/**/* - server/**/* + - test/scout/**/* - '!target/**/*' tasks: jest: diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/constants.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/constants.ts new file mode 100644 index 0000000000000..872da1a97fefd --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/constants.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export const SAMPLE_DATA_SET_ID = 'flights'; +export const SAMPLE_DATA_DASHBOARD_ID = '7adfa750-4c81-11e8-b3d7-01146121b73d'; +export const SAMPLE_DATA_VIEW = 'Kibana Sample Data Flights'; +export const SAMPLE_DATA_TIME_RANGE = 'sample_data range'; + +export const SAMPLE_DATA_RANGE = [ + { + from: 'now-30d', + to: 'now+40d', + display: 'sample data range', + }, + { + from: 'now/d', + to: 'now/d', + display: 'Today', + }, + { + from: 'now/w', + to: 'now/w', + display: 'This week', + }, + { + from: 'now-15m', + to: 'now', + display: 'Last 15 minutes', + }, + { + from: 'now-30m', + to: 'now', + display: 'Last 30 minutes', + }, + { + from: 'now-1h', + to: 'now', + display: 'Last 1 hour', + }, + { + from: 'now-24h', + to: 'now', + display: 'Last 24 hours', + }, + { + from: 'now-7d', + to: 'now', + display: 'Last 7 days', + }, + { + from: 'now-30d', + to: 'now', + display: 'Last 30 days', + }, + { + from: 'now-90d', + to: 'now', + display: 'Last 90 days', + }, + { + from: 'now-1y', + to: 'now', + display: 'Last 1 year', + }, +]; + +export const LENS_BASIC_KIBANA_ARCHIVE = + 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json'; +export const LENS_BASIC_DATA_VIEW = 'logstash-*'; +export const LENS_BASIC_TITLE = 'Artistpreviouslyknownaslens'; +export const LENS_BASIC_TIME_RANGE = { + from: 'Sep 22, 2015 @ 00:00:00.000', + to: 'Sep 23, 2015 @ 00:00:00.000', +}; + +export const DASHBOARD_SAVED_SEARCH_ARCHIVE = + 'src/platform/test/functional/fixtures/kbn_archiver/dashboard/current/kibana'; +export const DASHBOARD_DEFAULT_INDEX_TITLE = 'logstash-*'; + +export const MIGRATION_SMOKE_EXPORTS_DIR = + 'src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports'; +export const DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ = 'embeddablePanelAction-editPanel'; +export const SHAKESPEARE_DATA_VIEW_TITLE = 'shakespeare'; +export const LOGSTASH_DATA_VIEW_TITLE = 'logstash*'; diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel.playwright.config.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel.playwright.config.ts new file mode 100644 index 0000000000000..71bf44470ee6a --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel.playwright.config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { createPlaywrightConfig } from '@kbn/scout'; + +export default createPlaywrightConfig({ + testDir: './parallel_tests', + workers: 2, + runGlobalSetup: true, +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/async_dashboard.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/async_dashboard.spec.ts new file mode 100644 index 0000000000000..67f58332c3723 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/async_dashboard.spec.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import { UI_SETTINGS } from '@kbn/data-plugin/common'; +import { + SAMPLE_DATA_SET_ID, + SAMPLE_DATA_DASHBOARD_ID, + SAMPLE_DATA_VIEW, + SAMPLE_DATA_TIME_RANGE, + SAMPLE_DATA_RANGE, +} from '../constants'; + +spaceTest.describe('Flights dashboard (sample data)', { tag: tags.DEPLOYMENT_AGNOSTIC }, () => { + spaceTest.beforeAll(async ({ apiServices, scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + + // install sample flights data + await apiServices.sampleData.install(SAMPLE_DATA_SET_ID, scoutSpace.id); + + await expect + .poll( + async () => { + const { data: dataViews } = await apiServices.dataViews.find( + (dataView) => dataView.name === SAMPLE_DATA_VIEW || dataView.title === SAMPLE_DATA_VIEW, + scoutSpace.id + ); + return dataViews.length; + }, + // Sample data install can take longer than Scout's default polling window. + { timeout: 60_000 } + ) + .toBeGreaterThan(0); + + await scoutSpace.uiSettings.set({ + [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: JSON.stringify(SAMPLE_DATA_RANGE), + }); + }); + + spaceTest.afterAll(async ({ apiServices, scoutSpace }) => { + // remove sample flights data + await apiServices.sampleData.remove(SAMPLE_DATA_SET_ID, scoutSpace.id); + }); + + spaceTest('loads dashboard and renders panels', async ({ browserAuth, page, pageObjects }) => { + await spaceTest.step('login and prepare Discover', async () => { + await browserAuth.loginAsAdmin(); + await pageObjects.discover.goto(); + await pageObjects.discover.selectDataView(SAMPLE_DATA_VIEW); + await pageObjects.datePicker.setCommonlyUsedTime(SAMPLE_DATA_TIME_RANGE); + await pageObjects.discover.waitUntilSearchingHasFinished(); + expect(await pageObjects.discover.getHitCountInt()).toBeGreaterThan(0); + }); + + await spaceTest.step('open flights dashboard and validate panels', async () => { + await pageObjects.dashboard.openDashboardWithId(SAMPLE_DATA_DASHBOARD_ID); + await pageObjects.datePicker.setCommonlyUsedTime(SAMPLE_DATA_TIME_RANGE); + expect(await pageObjects.dashboard.getPanelCount()).toBeGreaterThan(0); + }); + + await spaceTest.step('return to dashboard and validate panels', async () => { + await pageObjects.dashboard.openDashboardWithId(SAMPLE_DATA_DASHBOARD_ID); + + await expect.poll(async () => await pageObjects.dashboard.getControlCount()).toBe(3); + + // check panels rendered + await expect.poll(async () => await pageObjects.dashboard.getPanelCount()).toBe(16); + + // check charts rendered + await expect.poll(async () => await page.testSubj.locator('xyVisChart').count()).toBe(5); + + // Checking saved searches rendered + await expect + .poll(async () => await pageObjects.dashboard.getSavedSearchRowCount()) + .toBeGreaterThan(10); + + // Checking tag clouds rendered + const legendLabels = page.locator('[data-testid="echLegendItemLabel"]'); + const legendTexts = await legendLabels.allInnerTexts(); + ['Sunny', 'Rain', 'Clear', 'Cloudy', 'Hail'].forEach((value) => { + expect(legendTexts).toContain(value); + }); + + // Checking vega chart rendered + await expect(page.locator('.vgaVis__view')).toBeVisible(); + }); + }); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_lens_by_value.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_lens_by_value.spec.ts new file mode 100644 index 0000000000000..c4fc589063731 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_lens_by_value.spec.ts @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import type { PageObjects } from '@kbn/scout'; +import { + LENS_BASIC_KIBANA_ARCHIVE, + LENS_BASIC_DATA_VIEW, + LENS_BASIC_TITLE, + LENS_BASIC_TIME_RANGE, +} from '../constants'; + +spaceTest.describe('Lens by-value panels (dashboard)', { tag: tags.DEPLOYMENT_AGNOSTIC }, () => { + let lensSavedObjectId = ''; + + spaceTest.beforeAll(async ({ scoutSpace }) => { + const importedObjects = await scoutSpace.savedObjects.load(LENS_BASIC_KIBANA_ARCHIVE); + const lensObject = importedObjects.find( + (savedObject) => savedObject.type === 'lens' && savedObject.title === LENS_BASIC_TITLE + ); + expect( + lensObject, + `Lens saved object "${LENS_BASIC_TITLE}" was not found in ${LENS_BASIC_KIBANA_ARCHIVE}` + ).toBeTruthy(); + lensSavedObjectId = lensObject!.id; + + await scoutSpace.uiSettings.setDefaultIndex(LENS_BASIC_DATA_VIEW); + await scoutSpace.uiSettings.setDefaultTime(LENS_BASIC_TIME_RANGE); + }); + + spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { + await browserAuth.loginAsPrivilegedUser(); + await pageObjects.dashboard.goto(); + await pageObjects.dashboard.openNewDashboard(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + const addByValueLensPanel = async (pageObjects: PageObjects) => { + await pageObjects.dashboard.addLens(LENS_BASIC_TITLE); + await pageObjects.dashboard.clonePanel(LENS_BASIC_TITLE); + await pageObjects.dashboard.removePanel(LENS_BASIC_TITLE); + }; + + spaceTest('by-value panel retains count after edit', async ({ pageObjects }) => { + await spaceTest.step('add by value panel', async () => { + await addByValueLensPanel(pageObjects); + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + }); + + const originalPanelCount = await pageObjects.dashboard.getPanelCount(); + + await spaceTest.step('edit panel in Lens and switch to treemap', async () => { + await pageObjects.dashboard.navigateToLensEditorFromPanel(`${LENS_BASIC_TITLE} (copy)`); + await pageObjects.lens.switchToVisualization('treemap'); + await pageObjects.lens.saveAndReturn(); + }); + + await spaceTest.step('verify panel count unchanged', async () => { + expect(await pageObjects.dashboard.getPanelCount()).toBe(originalPanelCount); + }); + }); + + spaceTest( + 'edits to a by value lens panel are properly applied', + async ({ pageObjects, page }) => { + await spaceTest.step('add by value panel', async () => { + await addByValueLensPanel(pageObjects); + }); + + await spaceTest.step('edit panel in Lens and switch to pie', async () => { + await pageObjects.dashboard.navigateToLensEditorFromPanel(`${LENS_BASIC_TITLE} (copy)`); + await pageObjects.lens.switchToVisualization('pie'); + await pageObjects.lens.saveAndReturn(); + }); + + await spaceTest.step('verify partition chart renders', async () => { + await expect(page.testSubj.locator('partitionVisChart')).toBeVisible(); + }); + } + ); + + spaceTest('saving to library keeps panel count', async ({ pageObjects }) => { + const newTitle = 'look out library, here I come!'; + + await spaceTest.step('add by value panel', async () => { + await addByValueLensPanel(pageObjects); + }); + + const originalPanelCount = await pageObjects.dashboard.getPanelCount(); + + await spaceTest.step('save panel to library from dashboard', async () => { + await pageObjects.dashboard.saveToLibrary(newTitle, `${LENS_BASIC_TITLE} (copy)`); + }); + + await spaceTest.step('verify panel count and title', async () => { + expect(await pageObjects.dashboard.getPanelCount()).toBe(originalPanelCount); + const titles = await pageObjects.dashboard.getPanelTitles(); + expect(titles).toContain(newTitle); + }); + }); + + spaceTest('opening Lens directly drops dashboard link', async ({ page, pageObjects }) => { + await spaceTest.step('open Lens editor directly', async () => { + await page.gotoApp('lens', { hash: `/edit/${lensSavedObjectId}` }); + await pageObjects.lens.waitForLensApp(); + }); + + await spaceTest.step('verify no return-to-origin switch', async () => { + await expect(page.testSubj.locator('returnToOriginModeSwitch')).toBeHidden(); + }); + }); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_maps_by_value.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_maps_by_value.spec.ts new file mode 100644 index 0000000000000..460529b1462a2 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_maps_by_value.spec.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import type { PageObjects, ScoutPage } from '@kbn/scout'; +import { LENS_BASIC_KIBANA_ARCHIVE } from '../constants'; + +const MAPS_LAYER_GROUP_TITLE = 'Layer group'; +const MAPS_LIBRARY_NAME_PREFIX = 'my map'; + +spaceTest.describe('Maps by-value panels (dashboard)', { tag: tags.DEPLOYMENT_AGNOSTIC }, () => { + let mapCounter = 0; + let dashboardUrl = ''; + + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.load(LENS_BASIC_KIBANA_ARCHIVE); + }); + + spaceTest.beforeEach(async ({ browserAuth, pageObjects, page }) => { + await browserAuth.loginAsPrivilegedUser(); + await pageObjects.dashboard.goto(); + await pageObjects.dashboard.openNewDashboard(); + dashboardUrl = page.url(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + }); + + const createAndAddMapByValue = async (pageObjects: PageObjects) => { + await pageObjects.dashboard.addMapPanel(); + await pageObjects.maps.waitForRenderComplete(); + await pageObjects.maps.saveAndReturnButton.click(); + await pageObjects.dashboard.waitForRenderComplete(); + }; + + const openMapEditorAndAddLayer = async (pageObjects: PageObjects) => { + const [panelTitle] = await pageObjects.dashboard.getPanelTitles(); + await pageObjects.dashboard.clickPanelAction('embeddablePanelAction-editPanel', panelTitle); + await pageObjects.maps.waitForRenderComplete(); + + await pageObjects.maps.addLayerButton.click(); + await pageObjects.maps.layerAddForm.waitFor({ state: 'visible' }); + await pageObjects.maps.selectLayerWizardByTitle(MAPS_LAYER_GROUP_TITLE); + await pageObjects.maps.importFileButton.click(); + }; + + const saveMapByValueAndReturn = async (pageObjects: PageObjects) => { + await pageObjects.maps.saveAndReturnButton.click(); + await pageObjects.dashboard.waitForRenderComplete(); + }; + + const saveMapToLibraryAndReturn = async (pageObjects: PageObjects) => { + await pageObjects.maps.saveButton.click(); + await pageObjects.maps.saveFromModal(`${MAPS_LIBRARY_NAME_PREFIX} ${mapCounter++}`, { + redirectToOrigin: true, + }); + await pageObjects.dashboard.waitForRenderComplete(); + }; + + const saveMapToLibraryAndStay = async (pageObjects: PageObjects, page: ScoutPage) => { + await pageObjects.maps.saveButton.click(); + await pageObjects.maps.saveFromModal(`${MAPS_LIBRARY_NAME_PREFIX} ${mapCounter++}`, { + redirectToOrigin: false, + }); + await page.goto(dashboardUrl); + await pageObjects.dashboard.waitForRenderComplete(); + }; + + spaceTest('adds a map by value', async ({ pageObjects }) => { + await spaceTest.step('add map panel', async () => { + await createAndAddMapByValue(pageObjects); + }); + + await spaceTest.step('verify panel count', async () => { + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + }); + }); + + spaceTest('editing a by-value map updates the panel', async ({ pageObjects }) => { + await spaceTest.step('add map panel', async () => { + await createAndAddMapByValue(pageObjects); + }); + + await spaceTest.step('edit map panel', async () => { + await openMapEditorAndAddLayer(pageObjects); + await saveMapByValueAndReturn(pageObjects); + }); + + await spaceTest.step('verify panel count and layer', async () => { + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + await expect(pageObjects.maps.getLayerToggleButton(MAPS_LAYER_GROUP_TITLE)).toBeVisible(); + }); + }); + + spaceTest('saving to library updates the panel', async ({ pageObjects }) => { + await spaceTest.step('add map panel', async () => { + await createAndAddMapByValue(pageObjects); + }); + + await spaceTest.step('edit map panel and save to library', async () => { + await openMapEditorAndAddLayer(pageObjects); + await saveMapToLibraryAndReturn(pageObjects); + }); + + await spaceTest.step('verify layer exists', async () => { + await expect(pageObjects.maps.getLayerToggleButton(MAPS_LAYER_GROUP_TITLE)).toBeVisible(); + }); + }); + + spaceTest( + 'saving to library without return does not update panel', + async ({ pageObjects, page }) => { + await spaceTest.step('add map panel', async () => { + await createAndAddMapByValue(pageObjects); + }); + + await spaceTest.step('save to library without redirect', async () => { + await openMapEditorAndAddLayer(pageObjects); + await saveMapToLibraryAndStay(pageObjects, page); + }); + + await spaceTest.step('verify layer does not exist', async () => { + await expect(pageObjects.maps.getLayerToggleButton(MAPS_LAYER_GROUP_TITLE)).toBeHidden(); + }); + } + ); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_panel_listing.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_panel_listing.spec.ts new file mode 100644 index 0000000000000..a8045869c5cd7 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_panel_listing.spec.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect } from '@kbn/scout'; +import { DASHBOARD_DEFAULT_INDEX_TITLE, DASHBOARD_SAVED_SEARCH_ARCHIVE } from '../constants'; + +// may include "observabilityGroup" panel group (and other panel groups) +const DASHBOARD_PANEL_GROUP_ORDER = [ + 'visualizationsGroup', + 'controlsGroup', + 'annotation-and-navigationGroup', + 'mlGroup', + 'legacyGroup', +]; + +const DASHBOARD_PANEL_TYPE_COUNT = 18; + +spaceTest.describe( + 'Dashboard panel listing', + { tag: ['@ess', '@svlSearch', '@svlSecurity'] }, + () => { + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + await scoutSpace.savedObjects.load(DASHBOARD_SAVED_SEARCH_ARCHIVE); + await scoutSpace.uiSettings.setDefaultIndex(DASHBOARD_DEFAULT_INDEX_TITLE); + }); + + spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { + await browserAuth.loginAsPrivilegedUser(); + await pageObjects.dashboard.goto(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + spaceTest('renders panel groups and panel count', async ({ pageObjects }) => { + await spaceTest.step('open new dashboard and add panel flyout', async () => { + await pageObjects.dashboard.openNewDashboard(); + await pageObjects.dashboard.openAddPanelFlyout(); + }); + + await spaceTest.step('verify panel groups order', async () => { + const panelGroupOrder = await pageObjects.dashboard.getPanelGroupOrder(); + expect(panelGroupOrder.length).toBeGreaterThanOrEqual(DASHBOARD_PANEL_GROUP_ORDER.length); + expect(panelGroupOrder).toStrictEqual(expect.arrayContaining(DASHBOARD_PANEL_GROUP_ORDER)); + }); + + await spaceTest.step('verify total panel count', async () => { + expect(await pageObjects.dashboard.getPanelTypeCount()).toBeGreaterThanOrEqual( + DASHBOARD_PANEL_TYPE_COUNT + ); + }); + }); + } +); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_panel_listing_obs_group.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_panel_listing_obs_group.spec.ts new file mode 100644 index 0000000000000..51d8816963c81 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_panel_listing_obs_group.spec.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect } from '@kbn/scout'; +import { DASHBOARD_DEFAULT_INDEX_TITLE, DASHBOARD_SAVED_SEARCH_ARCHIVE } from '../constants'; + +// includes "observabilityGroup" panel group +const DASHBOARD_PANEL_GROUP_ORDER = [ + 'visualizationsGroup', + 'controlsGroup', + 'annotation-and-navigationGroup', + 'mlGroup', + 'observabilityGroup', + 'legacyGroup', +]; + +const DASHBOARD_PANEL_TYPE_COUNT = 24; + +spaceTest.describe( + 'Dashboard panel listing (includes observability group)', + { tag: ['@ess', '@svlOblt'] }, + () => { + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + await scoutSpace.savedObjects.load(DASHBOARD_SAVED_SEARCH_ARCHIVE); + await scoutSpace.uiSettings.setDefaultIndex(DASHBOARD_DEFAULT_INDEX_TITLE); + }); + + spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { + await browserAuth.loginAsPrivilegedUser(); + await pageObjects.dashboard.goto(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + spaceTest('renders panel groups and panel count', async ({ pageObjects }) => { + await spaceTest.step('open new dashboard and add panel flyout', async () => { + await pageObjects.dashboard.openNewDashboard(); + await pageObjects.dashboard.openAddPanelFlyout(); + }); + + await spaceTest.step('verify panel groups order', async () => { + const panelGroupOrder = await pageObjects.dashboard.getPanelGroupOrder(); + expect(panelGroupOrder).toHaveLength(DASHBOARD_PANEL_GROUP_ORDER.length); + expect(panelGroupOrder).toStrictEqual(DASHBOARD_PANEL_GROUP_ORDER); + }); + + await spaceTest.step('verify total panel count', async () => { + expect(await pageObjects.dashboard.getPanelTypeCount()).toBe(DASHBOARD_PANEL_TYPE_COUNT); + }); + }); + } +); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_search_by_value.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_search_by_value.spec.ts new file mode 100644 index 0000000000000..9014ef9f61ca5 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/dashboard_search_by_value.spec.ts @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import { DASHBOARD_DEFAULT_INDEX_TITLE, DASHBOARD_SAVED_SEARCH_ARCHIVE } from '../constants'; + +const DASHBOARD_SAVED_SEARCH_NAME = 'Rendering-Test:-saved-search'; +const DASHBOARD_TIME_RANGE = { + from: 'Sep 22, 2015 @ 00:00:00.000', + to: 'Sep 23, 2015 @ 00:00:00.000', +}; + +spaceTest.describe('Saved search panels (dashboard)', { tag: tags.DEPLOYMENT_AGNOSTIC }, () => { + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.load(DASHBOARD_SAVED_SEARCH_ARCHIVE); + await scoutSpace.uiSettings.setDefaultIndex(DASHBOARD_DEFAULT_INDEX_TITLE); + await scoutSpace.uiSettings.setDefaultTime(DASHBOARD_TIME_RANGE); + }); + + spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { + await browserAuth.loginAsPrivilegedUser(); + await pageObjects.dashboard.goto(); + await pageObjects.dashboard.openNewDashboard(); + + // add saved search from library + await addSavedSearchPanel(pageObjects); + + // verify saved search rows + await expect + .poll(async () => pageObjects.dashboard.getSavedSearchRowCount()) + .toBeGreaterThan(0); + + // verify initial library link + const titles = await pageObjects.dashboard.getPanelTitles(); + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + await pageObjects.dashboard.expectLinkedToLibrary(titles[0]); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + const addSavedSearchPanel = async (pageObjects: { dashboard: any }) => { + await pageObjects.dashboard.addSavedSearch(DASHBOARD_SAVED_SEARCH_NAME); + await pageObjects.dashboard.waitForRenderComplete(); + }; + + spaceTest('cloning saved search creates by-value panel', async ({ pageObjects }) => { + await spaceTest.step('clone the panel', async () => { + const titles = await pageObjects.dashboard.getPanelTitles(); + await pageObjects.dashboard.clonePanel(titles[0]); + }); + + await spaceTest.step('verify clone link state', async () => { + const titles = await pageObjects.dashboard.getPanelTitles(); + expect(await pageObjects.dashboard.getPanelCount()).toBe(2); + await pageObjects.dashboard.expectLinkedToLibrary(titles[0]); + await pageObjects.dashboard.expectNotLinkedToLibrary(titles[1]); + }); + }); + + spaceTest('unlinking saved search makes it by value', async ({ pageObjects }) => { + await spaceTest.step('unlink panel from library', async () => { + const titles = await pageObjects.dashboard.getPanelTitles(); + await pageObjects.dashboard.unlinkFromLibrary(titles[0]); + }); + + await spaceTest.step('verify panel is by value', async () => { + const titles = await pageObjects.dashboard.getPanelTitles(); + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + await pageObjects.dashboard.expectNotLinkedToLibrary(titles[0]); + }); + }); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/global.setup.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/global.setup.ts new file mode 100644 index 0000000000000..dd77dd59aee0c --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/global.setup.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { globalSetupHook, tags } from '@kbn/scout'; + +// ES archives shared across all test suites +const ES_ARCHIVES = { + LOGSTASH_FUNCTIONAL: 'src/platform/test/functional/fixtures/es_archiver/logstash_functional', + DASHBOARD_DATA: 'src/platform/test/functional/fixtures/es_archiver/dashboard/current/data', + SHAKESPEARE: 'x-pack/platform/test/fixtures/es_archives/getting_started/shakespeare', +}; + +globalSetupHook( + 'Ingest ES data needed for Dashboard tests', + { tag: tags.DEPLOYMENT_AGNOSTIC }, + async ({ esArchiver, log }) => { + log.info('[setup] Loading ES archives for Dashboard Scout tests...'); + + await esArchiver.loadIfNeeded(ES_ARCHIVES.LOGSTASH_FUNCTIONAL); + await esArchiver.loadIfNeeded(ES_ARCHIVES.DASHBOARD_DATA); + await esArchiver.loadIfNeeded(ES_ARCHIVES.SHAKESPEARE); + + log.info('[setup] Dashboard ES archives loaded successfully'); + } +); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/controls_migration_smoke.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/controls_migration_smoke.spec.ts new file mode 100644 index 0000000000000..761ad94b4b5f0 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/controls_migration_smoke.spec.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import type { ScoutPage } from '@kbn/scout'; +import { + findImportedSavedObjectId, + getDashboardPanels, + openDashboard, +} from '../../../utils/migration_smoke_helpers'; +import { MIGRATION_SMOKE_EXPORTS_DIR, SHAKESPEARE_DATA_VIEW_TITLE } from '../../constants'; + +const EXPORT_PATH = `${MIGRATION_SMOKE_EXPORTS_DIR}/controls_dashboard_migration_test_8_0_0.json`; +const DASHBOARD_TITLE = '[8.0.0] Controls Dashboard'; + +let dashboardId = ''; + +const getControlIds = async (page: ScoutPage) => { + const controls = await page.locator('[data-control-id]').all(); + return Promise.all( + controls.map(async (control) => (await control.getAttribute('data-control-id')) ?? '') + ); +}; + +const openOptionsListPopover = async (page: ScoutPage, controlId: string) => { + await page.testSubj.click(`optionsList-control-${controlId}`); + await page.testSubj + .locator('optionsList-control-available-options') + .waitFor({ state: 'visible' }); +}; + +const closeOptionsListPopover = async (page: ScoutPage, controlId: string) => { + await page.testSubj.click(`optionsList-control-${controlId}`); + await page.testSubj.locator('optionsList-control-available-options').waitFor({ state: 'hidden' }); +}; + +const getAvailableOptionsCount = async (page: ScoutPage) => { + const availableOptions = page.testSubj.locator('optionsList-control-available-options'); + return Number((await availableOptions.getAttribute('data-option-count')) ?? '0'); +}; + +const getSelectionsString = async (page: ScoutPage, controlId: string) => { + const controlButton = page.testSubj.locator(`optionsList-control-${controlId}`); + const selections = controlButton.locator('[data-test-subj="optionsListSelections"]'); + const [selectionText = ''] = await selections.allInnerTexts(); + const buttonText = await controlButton.innerText(); + return (selectionText || buttonText).trim(); +}; + +spaceTest.describe('Controls migration smoke (8.0.0)', { tag: tags.ESS_ONLY }, () => { + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + const imported = await scoutSpace.savedObjects.load(EXPORT_PATH); + dashboardId = findImportedSavedObjectId(imported, 'dashboard', DASHBOARD_TITLE); + await scoutSpace.uiSettings.setDefaultIndex(SHAKESPEARE_DATA_VIEW_TITLE); + }); + + spaceTest.beforeEach(async ({ browserAuth }) => { + await browserAuth.loginAsViewer(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + spaceTest( + 'imports and renders controls without regressions', + async ({ page, pageObjects, kbnClient, scoutSpace }) => { + await spaceTest.step('open the migrated dashboard', async () => { + await openDashboard(page, dashboardId); + await page.reload(); + await pageObjects.dashboard.waitForRenderComplete(); + const panels = await getDashboardPanels(kbnClient, scoutSpace.id, dashboardId); + await expect(page.testSubj.locator('embeddablePanel')).toHaveCount(panels.length); + }); + + await spaceTest.step('verify panels and controls render', async () => { + await expect(page.testSubj.locator('embeddableError')).toHaveCount(0); + await expect(page.testSubj.locator('control-frame')).toHaveCount(2); + + const controlIds = await getControlIds(page); + for (const controlId of controlIds) { + const controlTitle = page.locator(`#control-title-${controlId}`); + await expect(controlTitle.locator('[data-test-subj="embeddableError"]')).toHaveCount(0); + } + }); + + await spaceTest.step('verify control titles and options', async () => { + const titles = await page.testSubj.locator('control-frame-title').allInnerTexts(); + const normalizedTitles = titles.map((title) => title.split('\n')[0].trim()); + expect(normalizedTitles).toStrictEqual(['Speaker Name', 'Play Name']); + + const [speakerControlId, playControlId] = await getControlIds(page); + + await openOptionsListPopover(page, speakerControlId); + await expect.poll(async () => getAvailableOptionsCount(page)).toBe(10); + await closeOptionsListPopover(page, speakerControlId); + + await openOptionsListPopover(page, playControlId); + await expect.poll(async () => getAvailableOptionsCount(page)).toBe(5); + await closeOptionsListPopover(page, playControlId); + }); + + await spaceTest.step('verify default control selections', async () => { + const [speakerControlId] = await getControlIds(page); + const selectionString = await getSelectionsString(page, speakerControlId); + expect(selectionString).toBe('HAMLET, ROMEO, JULIET, BRUTUS'); + }); + + await spaceTest.step('verify pie chart reflects selected options', async () => { + const partitionChart = page.testSubj.locator('partitionVisChart'); + await expect(partitionChart).toBeVisible(); + const legendItems = partitionChart.locator('.echLegendItem__label'); + await expect.poll(async () => await legendItems.count()).toBe(4); + }); + } + ); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.json b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.json new file mode 100644 index 0000000000000..bd1c1205db7bb --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.json @@ -0,0 +1,5 @@ +{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","title":"shakespeare","typeMeta":"{}"},"coreMigrationVersion":"8.1.0","id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","migrationVersion":{"index-pattern":"8.0.0"},"references":[],"type":"index-pattern","updated_at":"2021-11-25T17:46:44.515Z","version":"WzEwLDJd"} + +{"attributes":{"controlGroupInput":{"controlStyle":"oneLine","panelsJSON":"{\"ec64cb73-f891-4f48-bfa5-e3ca0df9920e\":{\"order\":1,\"width\":\"medium\",\"type\":\"optionsListControl\",\"explicitInput\":{\"title\":\"Speaker Name\",\"fieldName\":\"speaker\",\"id\":\"ec64cb73-f891-4f48-bfa5-e3ca0df9920e\",\"enhancements\":{},\"selectedOptions\":[\"HAMLET\",\"ROMEO\",\"JULIET\",\"BRUTUS\"]}},\"3b343fbe-c0a0-4bc5-825e-7c4fff42ad8f\":{\"order\":2,\"width\":\"medium\",\"type\":\"optionsListControl\",\"explicitInput\":{\"title\":\"Play Name\",\"fieldName\":\"play_name\",\"id\":\"3b343fbe-c0a0-4bc5-825e-7c4fff42ad8f\",\"enhancements\":{},\"selectedOptions\":[]}}}"},"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"syncColors\":false,\"hidePanelTitles\":false}","panelsJSON":"[{\"version\":\"8.1.0\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":0,\"w\":23,\"h\":15,\"i\":\"bee1d6e0-389a-4992-a802-d51a658e3faa\"},\"panelIndex\":\"bee1d6e0-389a-4992-a802-d51a658e3faa\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"visualizationType\":\"lnsXY\",\"type\":\"lens\",\"references\":[{\"id\":\"f60ebe00-4e0f-11ec-afa1-59364130c0f2\",\"name\":\"indexpattern-datasource-current-indexpattern\",\"type\":\"index-pattern\"},{\"id\":\"f60ebe00-4e0f-11ec-afa1-59364130c0f2\",\"name\":\"indexpattern-datasource-layer-623f6f95-c70a-4e42-9505-b2b428d3c86b\",\"type\":\"index-pattern\"}],\"state\":{\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"yLeftExtent\":{\"mode\":\"full\"},\"yRightExtent\":{\"mode\":\"full\"},\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"labelsOrientation\":{\"x\":0,\"yLeft\":0,\"yRight\":0},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"623f6f95-c70a-4e42-9505-b2b428d3c86b\",\"accessors\":[\"9f9547b8-c132-4634-80f6-ed70ba4ec689\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"layerType\":\"data\",\"xAccessor\":\"44c77394-0cbc-43ec-b97a-7c446631c77e\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"623f6f95-c70a-4e42-9505-b2b428d3c86b\":{\"columns\":{\"44c77394-0cbc-43ec-b97a-7c446631c77e\":{\"label\":\"Top values of play_name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"play_name\",\"isBucketed\":true,\"params\":{\"size\":5,\"orderBy\":{\"type\":\"column\",\"columnId\":\"9f9547b8-c132-4634-80f6-ed70ba4ec689\"},\"orderDirection\":\"desc\",\"otherBucket\":false,\"missingBucket\":false}},\"9f9547b8-c132-4634-80f6-ed70ba4ec689\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"44c77394-0cbc-43ec-b97a-7c446631c77e\",\"9f9547b8-c132-4634-80f6-ed70ba4ec689\"],\"incompleteColumns\":{}}}}}}},\"enhancements\":{}}},{\"version\":\"8.1.0\",\"type\":\"visualization\",\"gridData\":{\"x\":23,\"y\":0,\"w\":24,\"h\":15,\"i\":\"63eee8ef-0220-4089-9be9-13a9839cae17\"},\"panelIndex\":\"63eee8ef-0220-4089-9be9-13a9839cae17\",\"embeddableConfig\":{\"savedVis\":{\"id\":\"\",\"title\":\"\",\"description\":\"\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"nestedLegend\":false,\"truncateLegend\":true,\"maxLegendLines\":1,\"distinctColors\":false,\"isDonut\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"labels\":{\"show\":true,\"last_level\":true,\"values\":true,\"valuesFormat\":\"percent\",\"percentDecimals\":2,\"truncate\":100,\"position\":\"default\"}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speaker\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"f60ebe00-4e0f-11ec-afa1-59364130c0f2\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"enhancements\":{}}}]","timeRestore":false,"title":"[8.0.0] Controls Dashboard","version":1},"coreMigrationVersion":"8.1.0","id":"5bcfc8b0-4e10-11ec-afa1-59364130c0f2","migrationVersion":{"dashboard":"8.0.0"},"references":[{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"bee1d6e0-389a-4992-a802-d51a658e3faa:indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"bee1d6e0-389a-4992-a802-d51a658e3faa:indexpattern-datasource-layer-623f6f95-c70a-4e42-9505-b2b428d3c86b","type":"index-pattern"},{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"63eee8ef-0220-4089-9be9-13a9839cae17:kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"controlGroup_ec64cb73-f891-4f48-bfa5-e3ca0df9920e:optionsListDataView","type":"index-pattern"},{"id":"f60ebe00-4e0f-11ec-afa1-59364130c0f2","name":"controlGroup_3b343fbe-c0a0-4bc5-825e-7c4fff42ad8f:optionsListDataView","type":"index-pattern"}],"type":"dashboard","updated_at":"2021-11-25T17:48:02.981Z","version":"WzM5LDJd"} + +{"excludedObjects":[],"excludedObjectsCount":0,"exportedCount":2,"missingRefCount":0,"missingReferences":[]} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.ndjson b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.ndjson similarity index 100% rename from x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.ndjson rename to src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/controls_dashboard_migration_test_8_0_0.ndjson diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.json b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.json new file mode 100644 index 0000000000000..b640017f18a0a --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.json @@ -0,0 +1,13 @@ +{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","title":"shakespeare"},"coreMigrationVersion":"7.12.2","id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","migrationVersion":{"index-pattern":"7.11.0"},"references":[],"type":"index-pattern","updated_at":"2021-06-17T22:28:02.495Z","version":"WzEyLDJd"} + +{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"optionsJSON":"{\"useMargins\":true,\"hidePanelTitles\":false}","panelsJSON":"[]","timeRestore":false,"title":"Blank Destination Dashboard","version":1},"coreMigrationVersion":"7.12.2","id":"759faf20-cfbd-11eb-984d-af3b44ed60a7","migrationVersion":{"dashboard":"7.11.0"},"references":[],"type":"dashboard","updated_at":"2021-06-17T22:43:39.414Z","version":"WzI1MiwyXQ=="} + +{"attributes":{"state":{"datasourceStates":{"indexpattern":{"layers":{"8faa1a43-2c03-4277-b19b-575da8b59561":{"columnOrder":["20d61a13-4000-4df2-9d83-d9ec0c87b32a","6f1df118-ab3f-4f7a-b5ea-c38e0c11aefe"],"columns":{"20d61a13-4000-4df2-9d83-d9ec0c87b32a":{"dataType":"string","isBucketed":true,"label":"Top values of speaker","operationType":"terms","params":{"missingBucket":false,"orderBy":{"columnId":"6f1df118-ab3f-4f7a-b5ea-c38e0c11aefe","type":"column"},"orderDirection":"desc","otherBucket":true,"size":20},"scale":"ordinal","sourceField":"speaker"},"6f1df118-ab3f-4f7a-b5ea-c38e0c11aefe":{"dataType":"number","isBucketed":false,"label":"Count of records","operationType":"count","scale":"ratio","sourceField":"Records"}},"incompleteColumns":{}}}}},"filters":[{"$state":{"store":"appState"},"meta":{"alias":null,"disabled":false,"indexRefName":"filter-index-pattern-0","key":"play_name","negate":false,"params":{"query":"Hamlet"},"type":"phrase"},"query":{"match_phrase":{"play_name":"Hamlet"}}},{"$state":{"store":"appState"},"meta":{"alias":null,"disabled":false,"indexRefName":"filter-index-pattern-1","key":"speaker","negate":true,"params":{"query":"HAMLET"},"type":"phrase"},"query":{"match_phrase":{"speaker":"HAMLET"}}}],"query":{"language":"kuery","query":""},"visualization":{"layers":[{"categoryDisplay":"default","groups":["20d61a13-4000-4df2-9d83-d9ec0c87b32a","20d61a13-4000-4df2-9d83-d9ec0c87b32a","20d61a13-4000-4df2-9d83-d9ec0c87b32a","20d61a13-4000-4df2-9d83-d9ec0c87b32a","20d61a13-4000-4df2-9d83-d9ec0c87b32a","20d61a13-4000-4df2-9d83-d9ec0c87b32a","20d61a13-4000-4df2-9d83-d9ec0c87b32a"],"layerId":"8faa1a43-2c03-4277-b19b-575da8b59561","legendDisplay":"default","metric":"6f1df118-ab3f-4f7a-b5ea-c38e0c11aefe","nestedLegend":false,"numberDisplay":"percent"}],"shape":"donut"}},"title":"Lens by Reference With Various Filters","visualizationType":"lnsPie"},"coreMigrationVersion":"7.12.2","id":"bf5d7860-cfbb-11eb-984d-af3b44ed60a7","migrationVersion":{"lens":"7.12.0"},"references":[{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-layer-8faa1a43-2c03-4277-b19b-575da8b59561","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"filter-index-pattern-0","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"filter-index-pattern-1","type":"index-pattern"}],"type":"lens","updated_at":"2021-06-17T22:31:24.138Z","version":"WzgzLDJd"} + +{"attributes":{"state":{"datasourceStates":{"indexpattern":{"layers":{"b349d4ba-df44-415f-b0be-999b12f52213":{"columnOrder":["73bc446b-31e8-47a1-b7a1-9549bc81570a","45d911a5-6178-4d9a-a8b4-702a8377c859","89abee74-0f49-4e13-b0e1-2698af72c6f6"],"columns":{"45d911a5-6178-4d9a-a8b4-702a8377c859":{"dataType":"string","isBucketed":true,"label":"Top values of speaker","operationType":"terms","params":{"missingBucket":false,"orderBy":{"columnId":"89abee74-0f49-4e13-b0e1-2698af72c6f6","type":"column"},"orderDirection":"desc","otherBucket":true,"size":3},"scale":"ordinal","sourceField":"speaker"},"73bc446b-31e8-47a1-b7a1-9549bc81570a":{"dataType":"string","isBucketed":true,"label":"Top values of play_name","operationType":"terms","params":{"missingBucket":false,"orderBy":{"type":"alphabetical"},"orderDirection":"asc","otherBucket":true,"size":5},"scale":"ordinal","sourceField":"play_name"},"89abee74-0f49-4e13-b0e1-2698af72c6f6":{"dataType":"number","isBucketed":false,"label":"Average of speech_number","operationType":"avg","scale":"ratio","sourceField":"speech_number"}},"incompleteColumns":{}}}}},"filters":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["89abee74-0f49-4e13-b0e1-2698af72c6f6"],"layerId":"b349d4ba-df44-415f-b0be-999b12f52213","position":"top","seriesType":"bar_stacked","showGridlines":false,"splitAccessor":"45d911a5-6178-4d9a-a8b4-702a8377c859","xAccessor":"73bc446b-31e8-47a1-b7a1-9549bc81570a"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"[7.12.1] Lens By Reference with Average","visualizationType":"lnsXY"},"coreMigrationVersion":"7.12.2","id":"09ae9610-cfbc-11eb-984d-af3b44ed60a7","migrationVersion":{"lens":"7.12.0"},"references":[{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-layer-b349d4ba-df44-415f-b0be-999b12f52213","type":"index-pattern"}],"type":"lens","updated_at":"2021-06-17T22:33:28.820Z","version":"WzEyMywyXQ=="} + +{"attributes":{"state":{"datasourceStates":{"indexpattern":{"layers":{"7d197461-9572-4437-b565-f3d7ec731753":{"columnOrder":["de0485bc-e55f-45d1-bf14-2a252ff718d0","a8fced94-076e-44ac-9e94-c0e3847e51b5"],"columns":{"a8fced94-076e-44ac-9e94-c0e3847e51b5":{"dataType":"number","isBucketed":false,"label":"Average of speech_number","operationType":"avg","scale":"ratio","sourceField":"speech_number"},"de0485bc-e55f-45d1-bf14-2a252ff718d0":{"dataType":"string","isBucketed":true,"label":"Top values of type.keyword","operationType":"terms","params":{"missingBucket":false,"orderBy":{"columnId":"a8fced94-076e-44ac-9e94-c0e3847e51b5","type":"column"},"orderDirection":"desc","otherBucket":true,"size":5},"scale":"ordinal","sourceField":"type.keyword"}},"incompleteColumns":{}}}}},"filters":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"layers":[{"accessors":["a8fced94-076e-44ac-9e94-c0e3847e51b5"],"layerId":"7d197461-9572-4437-b565-f3d7ec731753","seriesType":"bar_stacked","xAccessor":"de0485bc-e55f-45d1-bf14-2a252ff718d0"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"[7.12.1] Lens By Reference with Drilldown","visualizationType":"lnsXY"},"coreMigrationVersion":"7.12.2","id":"8ac83fc0-cfbd-11eb-984d-af3b44ed60a7","migrationVersion":{"lens":"7.12.0"},"references":[{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-layer-7d197461-9572-4437-b565-f3d7ec731753","type":"index-pattern"}],"type":"lens","updated_at":"2021-06-17T22:44:14.911Z","version":"WzI2OSwyXQ=="} + +{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.12.2\",\"type\":\"lens\",\"gridData\":{\"x\":0,\"y\":0,\"w\":9,\"h\":15,\"i\":\"64bf6149-4bba-423e-91b4-9ff160f520e0\"},\"panelIndex\":\"64bf6149-4bba-423e-91b4-9ff160f520e0\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"type\":\"lens\",\"visualizationType\":\"lnsPie\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"8faa1a43-2c03-4277-b19b-575da8b59561\":{\"columns\":{\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\":{\"label\":\"Top values of speaker\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"speaker\",\"isBucketed\":true,\"params\":{\"size\":20,\"orderBy\":{\"type\":\"column\",\"columnId\":\"6f1df118-ab3f-4f7a-b5ea-c38e0c11aefe\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false}},\"6f1df118-ab3f-4f7a-b5ea-c38e0c11aefe\":{\"label\":\"Count of records\",\"dataType\":\"number\",\"operationType\":\"count\",\"isBucketed\":false,\"scale\":\"ratio\",\"sourceField\":\"Records\"}},\"columnOrder\":[\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\",\"6f1df118-ab3f-4f7a-b5ea-c38e0c11aefe\"],\"incompleteColumns\":{}}}}},\"visualization\":{\"shape\":\"donut\",\"layers\":[{\"layerId\":\"8faa1a43-2c03-4277-b19b-575da8b59561\",\"groups\":[\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\",\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\",\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\",\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\",\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\",\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\",\"20d61a13-4000-4df2-9d83-d9ec0c87b32a\"],\"metric\":\"6f1df118-ab3f-4f7a-b5ea-c38e0c11aefe\",\"numberDisplay\":\"percent\",\"categoryDisplay\":\"default\",\"legendDisplay\":\"default\",\"nestedLegend\":false}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[{\"meta\":{\"alias\":null,\"negate\":false,\"disabled\":false,\"type\":\"phrase\",\"key\":\"play_name\",\"params\":{\"query\":\"Hamlet\"},\"indexRefName\":\"filter-index-pattern-0\"},\"query\":{\"match_phrase\":{\"play_name\":\"Hamlet\"}},\"$state\":{\"store\":\"appState\"}},{\"meta\":{\"alias\":null,\"negate\":true,\"disabled\":false,\"type\":\"phrase\",\"key\":\"speaker\",\"params\":{\"query\":\"HAMLET\"},\"indexRefName\":\"filter-index-pattern-1\"},\"query\":{\"match_phrase\":{\"speaker\":\"HAMLET\"}},\"$state\":{\"store\":\"appState\"}}]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"472d30b0-cfbb-11eb-984d-af3b44ed60a7\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"472d30b0-cfbb-11eb-984d-af3b44ed60a7\",\"name\":\"indexpattern-datasource-layer-8faa1a43-2c03-4277-b19b-575da8b59561\"},{\"name\":\"filter-index-pattern-0\",\"type\":\"index-pattern\",\"id\":\"472d30b0-cfbb-11eb-984d-af3b44ed60a7\"},{\"name\":\"filter-index-pattern-1\",\"type\":\"index-pattern\",\"id\":\"472d30b0-cfbb-11eb-984d-af3b44ed60a7\"}]},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"[7.12.1] Lens by Value With Various Filters\"},{\"version\":\"7.12.2\",\"type\":\"lens\",\"gridData\":{\"x\":9,\"y\":0,\"w\":24,\"h\":15,\"i\":\"6ecd96ef-70cc-4d80-a5e5-a2d5b43a2236\"},\"panelIndex\":\"6ecd96ef-70cc-4d80-a5e5-a2d5b43a2236\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"type\":\"lens\",\"visualizationType\":\"lnsXY\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"b349d4ba-df44-415f-b0be-999b12f52213\":{\"columns\":{\"73bc446b-31e8-47a1-b7a1-9549bc81570a\":{\"label\":\"Top values of play_name\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"play_name\",\"isBucketed\":true,\"params\":{\"size\":5,\"orderBy\":{\"type\":\"alphabetical\"},\"orderDirection\":\"asc\",\"otherBucket\":true,\"missingBucket\":false}},\"89abee74-0f49-4e13-b0e1-2698af72c6f6\":{\"label\":\"Average of speech_number\",\"dataType\":\"number\",\"operationType\":\"avg\",\"sourceField\":\"speech_number\",\"isBucketed\":false,\"scale\":\"ratio\"},\"45d911a5-6178-4d9a-a8b4-702a8377c859\":{\"label\":\"Top values of speaker\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"speaker\",\"isBucketed\":true,\"params\":{\"size\":3,\"orderBy\":{\"type\":\"column\",\"columnId\":\"89abee74-0f49-4e13-b0e1-2698af72c6f6\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false}}},\"columnOrder\":[\"73bc446b-31e8-47a1-b7a1-9549bc81570a\",\"45d911a5-6178-4d9a-a8b4-702a8377c859\",\"89abee74-0f49-4e13-b0e1-2698af72c6f6\"],\"incompleteColumns\":{}}}}},\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"b349d4ba-df44-415f-b0be-999b12f52213\",\"accessors\":[\"89abee74-0f49-4e13-b0e1-2698af72c6f6\"],\"position\":\"top\",\"seriesType\":\"bar_stacked\",\"showGridlines\":false,\"xAccessor\":\"73bc446b-31e8-47a1-b7a1-9549bc81570a\",\"splitAccessor\":\"45d911a5-6178-4d9a-a8b4-702a8377c859\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"472d30b0-cfbb-11eb-984d-af3b44ed60a7\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"472d30b0-cfbb-11eb-984d-af3b44ed60a7\",\"name\":\"indexpattern-datasource-layer-b349d4ba-df44-415f-b0be-999b12f52213\"}]},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"[7.12.1] Lens By Value Lens with Average\"},{\"version\":\"7.12.2\",\"type\":\"lens\",\"gridData\":{\"x\":33,\"y\":0,\"w\":15,\"h\":15,\"i\":\"fed39777-b755-45f8-9efb-1203b4b3d7cf\"},\"panelIndex\":\"fed39777-b755-45f8-9efb-1203b4b3d7cf\",\"embeddableConfig\":{\"attributes\":{\"title\":\"\",\"type\":\"lens\",\"visualizationType\":\"lnsXY\",\"state\":{\"datasourceStates\":{\"indexpattern\":{\"layers\":{\"7d197461-9572-4437-b565-f3d7ec731753\":{\"columns\":{\"de0485bc-e55f-45d1-bf14-2a252ff718d0\":{\"label\":\"Top values of type.keyword\",\"dataType\":\"string\",\"operationType\":\"terms\",\"scale\":\"ordinal\",\"sourceField\":\"type.keyword\",\"isBucketed\":true,\"params\":{\"size\":5,\"orderBy\":{\"type\":\"column\",\"columnId\":\"a8fced94-076e-44ac-9e94-c0e3847e51b5\"},\"orderDirection\":\"desc\",\"otherBucket\":true,\"missingBucket\":false}},\"a8fced94-076e-44ac-9e94-c0e3847e51b5\":{\"label\":\"Average of speech_number\",\"dataType\":\"number\",\"operationType\":\"avg\",\"sourceField\":\"speech_number\",\"isBucketed\":false,\"scale\":\"ratio\"}},\"columnOrder\":[\"de0485bc-e55f-45d1-bf14-2a252ff718d0\",\"a8fced94-076e-44ac-9e94-c0e3847e51b5\"],\"incompleteColumns\":{}}}}},\"visualization\":{\"legend\":{\"isVisible\":true,\"position\":\"right\"},\"valueLabels\":\"hide\",\"fittingFunction\":\"None\",\"axisTitlesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"tickLabelsVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"gridlinesVisibilitySettings\":{\"x\":true,\"yLeft\":true,\"yRight\":true},\"preferredSeriesType\":\"bar_stacked\",\"layers\":[{\"layerId\":\"7d197461-9572-4437-b565-f3d7ec731753\",\"seriesType\":\"bar_stacked\",\"accessors\":[\"a8fced94-076e-44ac-9e94-c0e3847e51b5\"],\"xAccessor\":\"de0485bc-e55f-45d1-bf14-2a252ff718d0\"}]},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[]},\"references\":[{\"type\":\"index-pattern\",\"id\":\"472d30b0-cfbb-11eb-984d-af3b44ed60a7\",\"name\":\"indexpattern-datasource-current-indexpattern\"},{\"type\":\"index-pattern\",\"id\":\"472d30b0-cfbb-11eb-984d-af3b44ed60a7\",\"name\":\"indexpattern-datasource-layer-7d197461-9572-4437-b565-f3d7ec731753\"}]},\"hidePanelTitles\":false,\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"8934b2f2-b989-4b8c-8339-c95e387f4372\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"Test Drilldown\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"title\":\"[7.12.1] Lens By Value with Drilldown\"},{\"version\":\"7.12.2\",\"gridData\":{\"x\":0,\"y\":15,\"w\":9,\"h\":15,\"i\":\"eb826c7a-0ead-4c8d-99cf-d823388bb91d\"},\"panelIndex\":\"eb826c7a-0ead-4c8d-99cf-d823388bb91d\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"[7.12.1] Lens by Reference With Various Filters\",\"panelRefName\":\"panel_3\"},{\"version\":\"7.12.2\",\"gridData\":{\"x\":9,\"y\":15,\"w\":24,\"h\":15,\"i\":\"80a4927b-aa69-4c80-ad38-482c141d0b93\"},\"panelIndex\":\"80a4927b-aa69-4c80-ad38-482c141d0b93\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"enhancements\":{}},\"panelRefName\":\"panel_4\"},{\"version\":\"7.12.2\",\"gridData\":{\"x\":33,\"y\":15,\"w\":15,\"h\":15,\"i\":\"57a79145-1314-49f3-87e3-7c494cf55f64\"},\"panelIndex\":\"57a79145-1314-49f3-87e3-7c494cf55f64\",\"embeddableConfig\":{\"hidePanelTitles\":false,\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"8934b2f2-b989-4b8c-8339-c95e387f4372\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"Test Drilldown\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_5\"}]","timeRestore":false,"title":"[7.12.1] Lens By Value Test Dashboard","version":1},"coreMigrationVersion":"7.12.2","id":"60a5cfa0-cfbd-11eb-984d-af3b44ed60a7","migrationVersion":{"dashboard":"7.11.0"},"references":[{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-layer-8faa1a43-2c03-4277-b19b-575da8b59561","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"filter-index-pattern-0","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"filter-index-pattern-1","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-layer-b349d4ba-df44-415f-b0be-999b12f52213","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-current-indexpattern","type":"index-pattern"},{"id":"472d30b0-cfbb-11eb-984d-af3b44ed60a7","name":"indexpattern-datasource-layer-7d197461-9572-4437-b565-f3d7ec731753","type":"index-pattern"},{"id":"759faf20-cfbd-11eb-984d-af3b44ed60a7","name":"drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:8934b2f2-b989-4b8c-8339-c95e387f4372:dashboardId","type":"dashboard"},{"id":"759faf20-cfbd-11eb-984d-af3b44ed60a7","name":"drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:8934b2f2-b989-4b8c-8339-c95e387f4372:dashboardId","type":"dashboard"},{"id":"bf5d7860-cfbb-11eb-984d-af3b44ed60a7","name":"panel_3","type":"lens"},{"id":"09ae9610-cfbc-11eb-984d-af3b44ed60a7","name":"panel_4","type":"lens"},{"id":"8ac83fc0-cfbd-11eb-984d-af3b44ed60a7","name":"panel_5","type":"lens"}],"type":"dashboard","updated_at":"2021-06-17T22:44:36.881Z","version":"WzI3NSwyXQ=="} + +{"exportedCount":6,"missingRefCount":0,"missingReferences":[]} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.ndjson b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.ndjson similarity index 100% rename from x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.ndjson rename to src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/lens_dashboard_migration_test_7_12_1.ndjson diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.json b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.json new file mode 100644 index 0000000000000..3e74048278fc2 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.json @@ -0,0 +1,5 @@ +{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"64d009bf-b2cf-44ab-bfda-06cf883f2a24\"},\"panelIndex\":\"64d009bf-b2cf-44ab-bfda-06cf883f2a24\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"TSVB Dashboard\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{}}},\"hidePanelTitles\":false,\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"e7cb1014-a092-44e8-a4d7-12fb6ad2bb7f\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"Time Series Drilldown\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}},{\"eventId\":\"b5eebcad-8eb3-4654-bab4-785848686100\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"Drilldown 2\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"title\":\"Time Series\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"0a7a9a89-7d4b-4a53-97f2-7d30f6670450\"},\"panelIndex\":\"0a7a9a89-7d4b-4a53-97f2-7d30f6670450\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"metric\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"avg\",\"field\":\"memory\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"label\":\"\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"background_color_rules\":[{\"id\":\"e35e8210-e3f0-11eb-bc03-1f348ce358cc\"}],\"time_range_mode\":\"entire_time_range\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"dfb6a2b8-04a3-4694-b144-7d6105c9f5ad\",\"triggers\":[\"FILTER_TRIGGER\"],\"action\":{\"name\":\"Drilldown\",\"config\":{\"useCurrentFilters\":true,\"useCurrentDateRange\":true},\"factoryId\":\"DASHBOARD_TO_DASHBOARD_DRILLDOWN\"}}]}}},\"title\":\"Average Memory\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":15,\"w\":24,\"h\":15,\"i\":\"4ed295aa-b56e-4b3c-b749-89b21b3db498\"},\"panelIndex\":\"4ed295aa-b56e-4b3c-b749-89b21b3db498\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"top_n\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"terms\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"terms_field\":\"clientip\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"1d\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"bar_color_rules\":[{\"id\":\"506f5870-e3f1-11eb-bc03-1f348ce358cc\"}]},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Top N\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":24,\"y\":15,\"w\":24,\"h\":15,\"i\":\"10354fb6-de13-4331-92e9-5d60e0782ef6\"},\"panelIndex\":\"10354fb6-de13-4331-92e9-5d60e0782ef6\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"gauge\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"gauge_color_rules\":[{\"id\":\"a521c790-e3f1-11eb-bc03-1f348ce358cc\"}],\"gauge_width\":10,\"gauge_inner_width\":10,\"gauge_style\":\"half\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Guage\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":30,\"w\":24,\"h\":15,\"i\":\"c21ec51b-ba7d-4fe3-95ed-63c198852368\"},\"panelIndex\":\"c21ec51b-ba7d-4fe3-95ed-63c198852368\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"markdown\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"markdown\":\"The count was {{ count.last.raw }}\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"TSVB Markdown\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":24,\"y\":30,\"w\":24,\"h\":15,\"i\":\"1890c642-9782-43e0-851c-5ffb3775a67d\"},\"panelIndex\":\"1890c642-9782-43e0-851c-5ffb3775a67d\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"table\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"split_color_mode\":\"kibana\",\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"index_pattern\":\"\",\"interval\":\"1d\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"isModelInvalid\":false,\"bar_color_rules\":[{\"id\":\"d1f993b0-e3f1-11eb-bc03-1f348ce358cc\"}],\"pivot_id\":\"clientip\",\"pivot_type\":\"ip\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"TSVB Table\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":45,\"w\":24,\"h\":15,\"i\":\"da27208b-2425-41b7-b02a-ec456f7ef55e\"},\"panelIndex\":\"da27208b-2425-41b7-b02a-ec456f7ef55e\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"annotations\":[{\"fields\":\"bytes\",\"template\":\"Bytes {{bytes}}\",\"index_pattern\":\"logstash*\",\"query_string\":{\"query\":\"bytes > 18000\",\"language\":\"kuery\"},\"color\":\"#F00\",\"hidden\":false,\"icon\":\"fa-tag\",\"id\":\"f2f4e990-e4c1-11eb-b885-218b39edaf39\",\"ignore_global_filters\":1,\"ignore_panel_filters\":1,\"time_field\":\"@timestamp\"}],\"axis_formatter\":\"number\",\"axis_position\":\"left\",\"axis_scale\":\"normal\",\"background_color_rules\":[{\"id\":\"35d9b2e0-e4c2-11eb-b885-218b39edaf39\"}],\"default_index_pattern\":\"logstash*\",\"default_timefield\":\"@timestamp\",\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"index_pattern\":\"\",\"interval\":\"\",\"isModelInvalid\":false,\"series\":[{\"axis_position\":\"right\",\"chart_type\":\"line\",\"color\":\"#68BC00\",\"fill\":0.5,\"formatter\":\"number\",\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"label\":\"\",\"line_width\":1,\"metrics\":[{\"field\":\"bytes\",\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"max\"}],\"point_size\":1,\"separate_axis\":0,\"split_color_mode\":\"kibana\",\"split_mode\":\"everything\",\"stacked\":\"none\",\"type\":\"timeseries\"}],\"show_grid\":1,\"show_legend\":1,\"time_field\":\"\",\"tooltip_mode\":\"show_all\",\"type\":\"timeseries\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"TSVB w Annotations\"}]","refreshInterval":{"pause":true,"value":0},"timeFrom":"2015-09-21T10:30:00.000Z","timeRestore":true,"timeTo":"2015-09-22T12:00:00.000Z","title":"TSVB Index Pattern Smoke Test","version":1},"coreMigrationVersion":"7.12.1","id":"226f4380-e3f2-11eb-a821-bb6e72afa109","migrationVersion":{"dashboard":"7.11.0"},"references":[{"id":"226f4380-e3f2-11eb-a821-bb6e72afa109","name":"drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:e7cb1014-a092-44e8-a4d7-12fb6ad2bb7f:dashboardId","type":"dashboard"},{"id":"226f4380-e3f2-11eb-a821-bb6e72afa109","name":"drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:b5eebcad-8eb3-4654-bab4-785848686100:dashboardId","type":"dashboard"},{"id":"226f4380-e3f2-11eb-a821-bb6e72afa109","name":"drilldown:DASHBOARD_TO_DASHBOARD_DRILLDOWN:dfb6a2b8-04a3-4694-b144-7d6105c9f5ad:dashboardId","type":"dashboard"}],"type":"dashboard","updated_at":"2021-07-14T16:46:33.549Z","version":"WzY0MiwzXQ=="} + +{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","timeFieldName":"@timestamp","title":"logstash*"},"coreMigrationVersion":"7.12.1","id":"d0d5bf10-e3ef-11eb-a821-bb6e72afa109","migrationVersion":{"index-pattern":"7.11.0"},"references":[],"type":"index-pattern","updated_at":"2021-07-13T15:34:30.529Z","version":"WzMwOSwzXQ=="} + +{"exportedCount":2,"missingRefCount":0,"missingReferences":[]} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.ndjson b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.ndjson similarity index 100% rename from x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.ndjson rename to src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_12_1.ndjson diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.json b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.json new file mode 100644 index 0000000000000..312f78138595c --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.json @@ -0,0 +1,5 @@ +{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.13.3\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"90f0f762-f2ae-4400-b650-e699513c6dbf\"},\"panelIndex\":\"90f0f762-f2ae-4400-b650-e699513c6dbf\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"Non Kibana Index\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"time_range_mode\":\"entire_time_range\",\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"@timestamp\",\"index_pattern\":\"logstash*\",\"use_kibana_indexes\":false,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"isModelInvalid\":false},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"enhancements\":{}}},{\"version\":\"7.13.3\",\"type\":\"visualization\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"e7112256-80ef-4d79-836c-6480e0039179\"},\"panelIndex\":\"e7112256-80ef-4d79-836c-6480e0039179\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"metrics\",\"params\":{\"time_range_mode\":\"entire_time_range\",\"id\":\"61ca57f0-469d-11e7-af02-69e470af7417\",\"type\":\"timeseries\",\"series\":[{\"id\":\"61ca57f1-469d-11e7-af02-69e470af7417\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"61ca57f2-469d-11e7-af02-69e470af7417\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"number\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\"}],\"time_field\":\"\",\"use_kibana_indexes\":true,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"isModelInvalid\":false,\"index_pattern_ref_name\":\"metrics_e7112256-80ef-4d79-836c-6480e0039179_0_index_pattern\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Kibana Index\"}]","refreshInterval":{"pause":true,"value":0},"timeFrom":"2015-09-21T10:30:00.000Z","timeRestore":true,"timeTo":"2015-09-22T12:00:00.000Z","title":"TSVB 7.13.3","version":1},"coreMigrationVersion":"7.13.3","id":"d8c09ac0-e4ca-11eb-8015-4bb2e4229ebd","migrationVersion":{"dashboard":"7.13.1"},"references":[{"id":"d0d5bf10-e3ef-11eb-a821-bb6e72afa109","name":"e7112256-80ef-4d79-836c-6480e0039179:metrics_e7112256-80ef-4d79-836c-6480e0039179_0_index_pattern","type":"index_pattern"}],"type":"dashboard","updated_at":"2021-07-23T18:45:00.781Z","version":"Wzc1NCwxXQ=="} + +{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","timeFieldName":"@timestamp","title":"logstash*"},"coreMigrationVersion":"7.13.3","id":"d0d5bf10-e3ef-11eb-a821-bb6e72afa109","migrationVersion":{"index-pattern":"7.11.0"},"references":[],"type":"index-pattern","updated_at":"2021-07-23T18:43:39.839Z","version":"WzcyMiwxXQ=="} + +{"exportedCount":2,"missingRefCount":1,"missingReferences":[{"id":"d0d5bf10-e3ef-11eb-a821-bb6e72afa109","type":"index_pattern"}]} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.ndjson b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.ndjson similarity index 100% rename from x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.ndjson rename to src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/tsvb_dashboard_migration_test_7_13_3.ndjson diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.json b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.json new file mode 100644 index 0000000000000..ad8b03f6673e3 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.json @@ -0,0 +1,13 @@ +{"attributes":{"fieldAttrs":"{}","fields":"[]","runtimeFieldMap":"{}","title":"shakespeare"},"coreMigrationVersion":"7.12.1","id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","migrationVersion":{"index-pattern":"7.11.0"},"references":[],"type":"index-pattern","updated_at":"2021-07-16T18:56:14.524Z","version":"WzM4LDFd"} + +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Speaker Count (Area Chart By-Ref)","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Speaker Count (Area Chart By-Ref)\",\"type\":\"area\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Line Count\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speaker\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Speaker\"},\"schema\":\"segment\"}],\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Line Count\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Line Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}}}"},"coreMigrationVersion":"7.12.1","id":"9f90fae0-e66b-11eb-86e8-1ffd09dc5582","migrationVersion":{"visualization":"7.12.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2021-07-19T15:32:03.822Z","version":"WzU0NSwxXQ=="} + +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Tag Cloud","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Tag Cloud\",\"type\":\"tagcloud\",\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"text_entry.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":18,\"maxFontSize\":72,\"showLabel\":true}}"},"coreMigrationVersion":"7.12.1","id":"954e2df0-e66b-11eb-86e8-1ffd09dc5582","migrationVersion":{"visualization":"7.12.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2021-07-16T19:25:30.580Z","version":"WzIwMSwxXQ=="} + +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Unique Speakers by Play (Pie chart By-Ref)","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Unique Speakers by Play (Pie chart By-Ref)\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"params\":{\"field\":\"speaker\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"play_name\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}]}"},"coreMigrationVersion":"7.12.1","id":"6b818220-e66f-11eb-86e8-1ffd09dc5582","migrationVersion":{"visualization":"7.12.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2021-07-16T19:52:58.437Z","version":"WzMxMiwxXQ=="} + +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"speaker : \\\"HAMLET\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"title":"Hamlet Speaking Overtime (Area chart By-Ref)","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Hamlet Speaking Overtime (Area chart By-Ref)\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speech_number\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}]}"},"coreMigrationVersion":"7.12.1","id":"3b53b990-e8a6-11eb-86e8-1ffd09dc5582","migrationVersion":{"visualization":"7.12.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"visualization","updated_at":"2021-07-19T15:30:22.251Z","version":"WzUxMSwxXQ=="} + +{"attributes":{"description":"","hits":0,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"language\":\"kuery\",\"query\":\"\"},\"filter\":[]}"},"optionsJSON":"{\"hidePanelTitles\":false,\"useMargins\":true}","panelsJSON":"[{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"b05dcb4e-d866-43cd-a6af-d26def3f6231\"},\"panelIndex\":\"b05dcb4e-d866-43cd-a6af-d26def3f6231\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Line Count\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"histogram\",\"mode\":\"stacked\",\"data\":{\"label\":\"Line Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{\"customLabel\":\"Line Count\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speaker\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":10,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\",\"customLabel\":\"Speaker\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Speaker Count (Area Chart By-Value)\"},{\"version\":\"7.12.1\",\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":15,\"i\":\"52747d95-33a3-4c36-b870-4a23f3e4dfec\"},\"panelIndex\":\"52747d95-33a3-4c36-b870-4a23f3e4dfec\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_1\"},{\"version\":\"7.12.1\",\"gridData\":{\"x\":24,\"y\":15,\"w\":24,\"h\":15,\"i\":\"d7df311d-e0cf-45f2-82cf-80b5a891fa9a\"},\"panelIndex\":\"d7df311d-e0cf-45f2-82cf-80b5a891fa9a\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"Word Cloud\",\"description\":\"\",\"type\":\"tagcloud\",\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":18,\"maxFontSize\":72,\"showLabel\":true},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"text_entry.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Words (Tag Cloud By-Ref)\",\"panelRefName\":\"panel_2\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":15,\"w\":24,\"h\":15,\"i\":\"ab307583-e63e-41fe-adc6-5be5f9b8b053\"},\"panelIndex\":\"ab307583-e63e-41fe-adc6-5be5f9b8b053\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"tagcloud\",\"params\":{\"scale\":\"linear\",\"orientation\":\"single\",\"minFontSize\":18,\"maxFontSize\":72,\"showLabel\":true},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"text_entry.keyword\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Words (Tag Cloud By-Value)\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":30,\"w\":24,\"h\":15,\"i\":\"d6be9fb1-7f48-4175-a29a-fb80420cceb9\"},\"panelIndex\":\"d6be9fb1-7f48-4175-a29a-fb80420cceb9\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"pie\",\"params\":{\"type\":\"pie\",\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"isDonut\":true,\"labels\":{\"show\":false,\"values\":true,\"last_level\":true,\"truncate\":100}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"cardinality\",\"params\":{\"field\":\"speaker\"},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"play_name\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":5,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"b8203dae-afe5-4d64-9c13-8ad62206b8e1\",\"triggers\":[\"VALUE_CLICK_TRIGGER\"],\"action\":{\"name\":\"Shakespeare Search\",\"config\":{\"url\":{\"template\":\"https://shakespeare.folger.edu/search/?search_text={{event.value}}\"},\"openInNewTab\":true,\"encodeUrl\":true},\"factoryId\":\"URL_DRILLDOWN\"}}]}}},\"title\":\"Unique Speakers by Play (Pie chart By-Value)\"},{\"version\":\"7.12.1\",\"gridData\":{\"x\":24,\"y\":30,\"w\":24,\"h\":15,\"i\":\"5098cff8-0d4f-4a71-8d1b-18d19a018b1f\"},\"panelIndex\":\"5098cff8-0d4f-4a71-8d1b-18d19a018b1f\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[{\"eventId\":\"910c4965-8781-49b9-9d6d-18ea8241a96a\",\"triggers\":[\"VALUE_CLICK_TRIGGER\"],\"action\":{\"name\":\"Shakespeare Search\",\"config\":{\"url\":{\"template\":\"https://shakespeare.folger.edu/search/?search_text={{event.value}}\"},\"openInNewTab\":true,\"encodeUrl\":true},\"factoryId\":\"URL_DRILLDOWN\"}}]}}},\"panelRefName\":\"panel_5\"},{\"version\":\"7.12.1\",\"type\":\"visualization\",\"gridData\":{\"x\":0,\"y\":45,\"w\":24,\"h\":15,\"i\":\"2fc385e6-dfe5-49d6-8302-62c17d7a50a4\"},\"panelIndex\":\"2fc385e6-dfe5-49d6-8302-62c17d7a50a4\",\"embeddableConfig\":{\"savedVis\":{\"title\":\"\",\"description\":\"\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"filter\":true,\"truncate\":100},\"title\":{},\"style\":{}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"},\"style\":{}}],\"seriesParams\":[{\"show\":true,\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"lineWidth\":2,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"detailedTooltip\":true,\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"addLegend\":true,\"legendPosition\":\"right\",\"fittingFunction\":\"linear\",\"times\":[],\"addTimeMarker\":false,\"radiusRatio\":9,\"thresholdLine\":{\"show\":false,\"value\":10,\"width\":1,\"style\":\"full\",\"color\":\"#E7664C\"},\"labels\":{}},\"uiState\":{},\"data\":{\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"params\":{},\"schema\":\"metric\"},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"params\":{\"field\":\"speech_number\",\"orderBy\":\"1\",\"order\":\"desc\",\"size\":100,\"otherBucket\":false,\"otherBucketLabel\":\"Other\",\"missingBucket\":false,\"missingBucketLabel\":\"Missing\"},\"schema\":\"segment\"}],\"searchSource\":{\"index\":\"7e9d4c70-e667-11eb-86e8-1ffd09dc5582\",\"query\":{\"query\":\"speaker : \\\"HAMLET\\\" \",\"language\":\"kuery\"},\"filter\":[]}}},\"hidePanelTitles\":false,\"enhancements\":{}},\"title\":\"Hamlet Speaking Overtime (Area chart By-Value)\"},{\"version\":\"7.12.1\",\"gridData\":{\"x\":24,\"y\":45,\"w\":24,\"h\":15,\"i\":\"964c5ffe-aae3-40b1-8240-14c5a218bbe2\"},\"panelIndex\":\"964c5ffe-aae3-40b1-8240-14c5a218bbe2\",\"embeddableConfig\":{\"enhancements\":{}},\"panelRefName\":\"panel_7\"}]","timeRestore":false,"title":"[7.12.1] Visualize Test Dashboard","version":1},"coreMigrationVersion":"7.12.1","id":"8a8f5a90-e668-11eb-86e8-1ffd09dc5582","migrationVersion":{"dashboard":"7.11.0"},"references":[{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"7e9d4c70-e667-11eb-86e8-1ffd09dc5582","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"},{"id":"9f90fae0-e66b-11eb-86e8-1ffd09dc5582","name":"panel_1","type":"visualization"},{"id":"954e2df0-e66b-11eb-86e8-1ffd09dc5582","name":"panel_2","type":"visualization"},{"id":"6b818220-e66f-11eb-86e8-1ffd09dc5582","name":"panel_5","type":"visualization"},{"id":"3b53b990-e8a6-11eb-86e8-1ffd09dc5582","name":"panel_7","type":"visualization"}],"type":"dashboard","updated_at":"2021-07-19T15:49:46.191Z","version":"WzU4NywxXQ=="} + +{"exportedCount":6,"missingRefCount":0,"missingReferences":[]} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.ndjson b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.ndjson similarity index 100% rename from x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.ndjson rename to src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/exports/visualize_dashboard_migration_test_7_12_1.ndjson diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/lens_migration_smoke.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/lens_migration_smoke.spec.ts new file mode 100644 index 0000000000000..a624a46defc67 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/lens_migration_smoke.spec.ts @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import { + ensureIndexPatternFromExport, + findImportedSavedObjectId, + getDashboardPanels, +} from '../../../utils/migration_smoke_helpers'; +import { + DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ, + MIGRATION_SMOKE_EXPORTS_DIR, + SHAKESPEARE_DATA_VIEW_TITLE, +} from '../../constants'; + +const EXPORT_PATH = `${MIGRATION_SMOKE_EXPORTS_DIR}/lens_dashboard_migration_test_7_12_1.json`; +const DASHBOARD_TITLE = '[7.12.1] Lens By Value Test Dashboard'; + +let dashboardId = ''; + +const getPanelDrilldownCounts = ( + panels: Array<{ + embeddableConfig?: { enhancements?: { dynamicActions?: { events?: unknown[] } } }; + }> +) => { + let panelsWithDrilldowns = 0; + let drilldownCount = 0; + for (const panel of panels) { + const events = panel.embeddableConfig?.enhancements?.dynamicActions?.events ?? []; + panelsWithDrilldowns += Number(events.length > 0); + drilldownCount += events.length; + } + + return { panelsWithDrilldowns, drilldownCount }; +}; + +spaceTest.describe('Lens migration smoke (7.12.1)', { tag: tags.ESS_ONLY }, () => { + spaceTest.beforeAll(async ({ scoutSpace, kbnClient }) => { + await scoutSpace.savedObjects.cleanStandardList(); + const imported = await scoutSpace.savedObjects.load(EXPORT_PATH); + dashboardId = findImportedSavedObjectId(imported, 'dashboard', DASHBOARD_TITLE); + await ensureIndexPatternFromExport(kbnClient, scoutSpace.id, EXPORT_PATH); + await scoutSpace.uiSettings.setDefaultIndex(SHAKESPEARE_DATA_VIEW_TITLE); + }); + + spaceTest.beforeEach(async ({ browserAuth }) => { + await browserAuth.loginAsPrivilegedUser(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + spaceTest( + 'imports and renders Lens panels without regressions', + async ({ page, pageObjects, kbnClient, scoutSpace }) => { + await spaceTest.step('open the migrated dashboard', async () => { + await pageObjects.dashboard.openDashboardWithId(dashboardId); + const panels = await getDashboardPanels(kbnClient, scoutSpace.id, dashboardId); + expect(await pageObjects.dashboard.getPanelCount()).toBe(panels.length); + }); + + await spaceTest.step('verify panels render without errors', async () => { + await expect(page.testSubj.locator('embeddableError')).toHaveCount(0); + }); + + await spaceTest.step('verify panel actions and drilldowns', async () => { + await pageObjects.dashboard.switchToEditMode(); + const panelTitles = await pageObjects.dashboard.getPanelTitles(); + for (const title of panelTitles) { + expect( + await pageObjects.dashboard.panelHasAction( + DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ, + title || undefined + ) + ).toBe(true); + } + + const panels = await getDashboardPanels(kbnClient, scoutSpace.id, dashboardId); + const { panelsWithDrilldowns } = getPanelDrilldownCounts(panels); + expect(panelsWithDrilldowns).toBe(2); + }); + } + ); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/tsvb_migration_smoke_7_12_1.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/tsvb_migration_smoke_7_12_1.spec.ts new file mode 100644 index 0000000000000..9ef428c07169f --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/tsvb_migration_smoke_7_12_1.spec.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import { + findImportedSavedObjectId, + getDashboardPanels, +} from '../../../utils/migration_smoke_helpers'; +import { + DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ, + LOGSTASH_DATA_VIEW_TITLE, + MIGRATION_SMOKE_EXPORTS_DIR, +} from '../../constants'; + +const EXPORT_PATH = `${MIGRATION_SMOKE_EXPORTS_DIR}/tsvb_dashboard_migration_test_7_12_1.json`; +const DASHBOARD_TITLE = 'TSVB Index Pattern Smoke Test'; + +let dashboardId = ''; + +const getPanelDrilldownCounts = ( + panels: Array<{ + embeddableConfig?: { enhancements?: { dynamicActions?: { events?: unknown[] } } }; + }> +) => { + let panelsWithDrilldowns = 0; + let drilldownCount = 0; + for (const panel of panels) { + const events = panel.embeddableConfig?.enhancements?.dynamicActions?.events ?? []; + panelsWithDrilldowns += Number(events.length > 0); + drilldownCount += events.length; + } + + return { panelsWithDrilldowns, drilldownCount }; +}; + +spaceTest.describe('TSVB migration smoke (7.12.1)', { tag: tags.ESS_ONLY }, () => { + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + const imported = await scoutSpace.savedObjects.load(EXPORT_PATH); + dashboardId = findImportedSavedObjectId(imported, 'dashboard', DASHBOARD_TITLE); + await scoutSpace.uiSettings.setDefaultIndex(LOGSTASH_DATA_VIEW_TITLE); + }); + + spaceTest.beforeEach(async ({ browserAuth }) => { + await browserAuth.loginAsPrivilegedUser(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + spaceTest( + 'imports and renders TSVB panels without regressions', + async ({ page, pageObjects, kbnClient, scoutSpace }) => { + await spaceTest.step('open the migrated dashboard', async () => { + await pageObjects.dashboard.openDashboardWithId(dashboardId); + const panels = await getDashboardPanels(kbnClient, scoutSpace.id, dashboardId); + expect(await pageObjects.dashboard.getPanelCount()).toBe(panels.length); + }); + + await spaceTest.step('verify panels render without errors', async () => { + await expect(page.testSubj.locator('embeddableError')).toHaveCount(0); + }); + + await spaceTest.step('verify panel actions and drilldowns', async () => { + await pageObjects.dashboard.switchToEditMode(); + const panelTitles = await pageObjects.dashboard.getPanelTitles(); + for (const title of panelTitles) { + expect( + await pageObjects.dashboard.panelHasAction( + DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ, + title || undefined + ) + ).toBe(true); + } + + const panels = await getDashboardPanels(kbnClient, scoutSpace.id, dashboardId); + const { panelsWithDrilldowns, drilldownCount } = getPanelDrilldownCounts(panels); + expect(panelsWithDrilldowns).toBe(2); + expect(drilldownCount).toBe(3); + }); + } + ); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/tsvb_migration_smoke_7_13_3.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/tsvb_migration_smoke_7_13_3.spec.ts new file mode 100644 index 0000000000000..d1ded7c8c0a05 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/tsvb_migration_smoke_7_13_3.spec.ts @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import { + ensureIndexPatternFromExport, + findImportedSavedObjectId, + getDashboardPanels, +} from '../../../utils/migration_smoke_helpers'; +import { + DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ, + LOGSTASH_DATA_VIEW_TITLE, + MIGRATION_SMOKE_EXPORTS_DIR, +} from '../../constants'; + +const EXPORT_PATH = `${MIGRATION_SMOKE_EXPORTS_DIR}/tsvb_dashboard_migration_test_7_13_3.json`; +const DASHBOARD_TITLE = 'TSVB 7.13.3'; + +let dashboardId = ''; + +spaceTest.describe('TSVB migration smoke (7.13.3)', { tag: tags.ESS_ONLY }, () => { + spaceTest.beforeAll(async ({ scoutSpace, kbnClient }) => { + await scoutSpace.savedObjects.cleanStandardList(); + const imported = await scoutSpace.savedObjects.load(EXPORT_PATH); + dashboardId = findImportedSavedObjectId(imported, 'dashboard', DASHBOARD_TITLE); + await ensureIndexPatternFromExport(kbnClient, scoutSpace.id, EXPORT_PATH); + await scoutSpace.uiSettings.setDefaultIndex(LOGSTASH_DATA_VIEW_TITLE); + }); + + spaceTest.beforeEach(async ({ browserAuth }) => { + await browserAuth.loginAsPrivilegedUser(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + spaceTest( + 'imports and renders TSVB panels without regressions', + async ({ page, pageObjects, kbnClient, scoutSpace }) => { + await spaceTest.step('open the migrated dashboard', async () => { + await pageObjects.dashboard.openDashboardWithId(dashboardId); + const panels = await getDashboardPanels(kbnClient, scoutSpace.id, dashboardId); + expect(await pageObjects.dashboard.getPanelCount()).toBe(panels.length); + }); + + await spaceTest.step('verify panels render without errors', async () => { + await expect(page.testSubj.locator('embeddableError')).toHaveCount(0); + }); + + await spaceTest.step('verify panel actions', async () => { + await pageObjects.dashboard.switchToEditMode(); + const panelTitles = await pageObjects.dashboard.getPanelTitles(); + for (const title of panelTitles) { + expect( + await pageObjects.dashboard.panelHasAction( + DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ, + title || undefined + ) + ).toBe(true); + } + }); + } + ); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/visualize_migration_smoke.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/visualize_migration_smoke.spec.ts new file mode 100644 index 0000000000000..79f84271f0ed9 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/migration_smoke_tests/visualize_migration_smoke.spec.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import { + ensureIndexPatternFromExport, + findImportedSavedObjectId, + getDashboardPanels, +} from '../../../utils/migration_smoke_helpers'; +import { + DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ, + MIGRATION_SMOKE_EXPORTS_DIR, + SHAKESPEARE_DATA_VIEW_TITLE, +} from '../../constants'; + +const EXPORT_PATH = `${MIGRATION_SMOKE_EXPORTS_DIR}/visualize_dashboard_migration_test_7_12_1.json`; +const DASHBOARD_TITLE = '[7.12.1] Visualize Test Dashboard'; + +let dashboardId = ''; + +const getPanelDrilldownCounts = ( + panels: Array<{ + embeddableConfig?: { enhancements?: { dynamicActions?: { events?: unknown[] } } }; + }> +) => { + let panelsWithDrilldowns = 0; + for (const panel of panels) { + const events = panel.embeddableConfig?.enhancements?.dynamicActions?.events ?? []; + panelsWithDrilldowns += Number(events.length > 0); + } + + return { panelsWithDrilldowns }; +}; + +spaceTest.describe('Visualize migration smoke (7.12.1)', { tag: tags.ESS_ONLY }, () => { + spaceTest.beforeAll(async ({ scoutSpace, kbnClient }) => { + await scoutSpace.savedObjects.cleanStandardList(); + const imported = await scoutSpace.savedObjects.load(EXPORT_PATH); + dashboardId = findImportedSavedObjectId(imported, 'dashboard', DASHBOARD_TITLE); + await ensureIndexPatternFromExport(kbnClient, scoutSpace.id, EXPORT_PATH); + await scoutSpace.uiSettings.setDefaultIndex(SHAKESPEARE_DATA_VIEW_TITLE); + }); + + spaceTest.beforeEach(async ({ browserAuth }) => { + await browserAuth.loginAsPrivilegedUser(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + spaceTest( + 'imports and renders Visualize panels without regressions', + async ({ page, pageObjects, kbnClient, scoutSpace }) => { + await spaceTest.step('open the migrated dashboard', async () => { + await pageObjects.dashboard.openDashboardWithId(dashboardId); + const panels = await getDashboardPanels(kbnClient, scoutSpace.id, dashboardId); + expect(await pageObjects.dashboard.getPanelCount()).toBe(panels.length); + }); + + await spaceTest.step('verify panels render without errors', async () => { + await expect(page.testSubj.locator('embeddableError')).toHaveCount(0); + }); + + await spaceTest.step('verify panel actions and drilldowns', async () => { + await pageObjects.dashboard.switchToEditMode(); + const panelTitles = await pageObjects.dashboard.getPanelTitles(); + for (const title of panelTitles) { + expect( + await pageObjects.dashboard.panelHasAction( + DASHBOARD_EDIT_PANEL_ACTION_TEST_SUBJ, + title || undefined + ) + ).toBe(true); + } + + const panels = await getDashboardPanels(kbnClient, scoutSpace.id, dashboardId); + const { panelsWithDrilldowns } = getPanelDrilldownCounts(panels); + expect(panelsWithDrilldowns).toBe(2); + }); + } + ); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/panel_time_range.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/panel_time_range.spec.ts new file mode 100644 index 0000000000000..91999ff61466a --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/panel_time_range.spec.ts @@ -0,0 +1,148 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import type { PageObjects } from '@kbn/scout'; +import { + LENS_BASIC_KIBANA_ARCHIVE, + LENS_BASIC_DATA_VIEW, + LENS_BASIC_TIME_RANGE, +} from '../constants'; + +const DASHBOARD_NAME_PREFIX = 'Panel time range'; + +const createDashboard = async (pageObjects: PageObjects, dashboardName: string) => { + await pageObjects.dashboard.goto(); + await pageObjects.dashboard.openNewDashboard(); + await pageObjects.dashboard.saveDashboard(dashboardName); +}; + +const addByValueLensPanel = async (pageObjects: PageObjects) => { + await pageObjects.dashboard.openNewLensPanel(); + await pageObjects.lens.configureXYDimensions(); + await pageObjects.lens.saveAndReturn(); +}; + +const applyCustomTimeRange = async (pageObjects: PageObjects, panelTitle?: string) => { + await pageObjects.dashboard.openCustomizePanel(panelTitle); + await pageObjects.dashboard.enableCustomTimeRange(); + await pageObjects.dashboard.openDatePickerQuickMenu(); + await pageObjects.dashboard.clickCommonlyUsedTimeRange('Last_30 days'); + await pageObjects.dashboard.saveCustomizePanel(); +}; + +const removeCustomTimeRange = async (pageObjects: PageObjects) => { + await pageObjects.dashboard.clickTimeRangeBadge(); + await pageObjects.dashboard.disableCustomTimeRange(); + await pageObjects.dashboard.saveCustomizePanel(); +}; + +spaceTest.describe('Panel time range (dashboard)', { tag: tags.DEPLOYMENT_AGNOSTIC }, () => { + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + await scoutSpace.savedObjects.load(LENS_BASIC_KIBANA_ARCHIVE); + await scoutSpace.uiSettings.setDefaultIndex(LENS_BASIC_DATA_VIEW); + await scoutSpace.uiSettings.setDefaultTime(LENS_BASIC_TIME_RANGE); + }); + + spaceTest.beforeEach(async ({ browserAuth }) => { + await browserAuth.loginAsPrivilegedUser(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + spaceTest( + 'by value: can add a custom time range to a panel', + async ({ pageObjects }, testInfo) => { + await spaceTest.step('create dashboard and add by-value lens panel', async () => { + await createDashboard(pageObjects, `${DASHBOARD_NAME_PREFIX} - ${testInfo.title}`); + await addByValueLensPanel(pageObjects); + }); + + await spaceTest.step('apply custom time range and verify badge', async () => { + await applyCustomTimeRange(pageObjects); + await pageObjects.dashboard.expectTimeRangeBadgeExists(); + await pageObjects.dashboard.expectEmptyPlaceholderVisible(); + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + await pageObjects.dashboard.clickQuickSave(); + }); + } + ); + + spaceTest( + 'by value: can remove a custom time range from a panel', + async ({ pageObjects }, testInfo) => { + await spaceTest.step('create dashboard and add by-value lens panel', async () => { + await createDashboard(pageObjects, `${DASHBOARD_NAME_PREFIX} - ${testInfo.title}`); + await addByValueLensPanel(pageObjects); + }); + + await spaceTest.step('apply custom time range', async () => { + await applyCustomTimeRange(pageObjects); + await pageObjects.dashboard.expectTimeRangeBadgeExists(); + }); + + await spaceTest.step('remove custom time range and verify panel', async () => { + await removeCustomTimeRange(pageObjects); + await pageObjects.dashboard.expectTimeRangeBadgeMissing(); + await pageObjects.dashboard.expectXYVisChartVisible(); + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + }); + } + ); + + spaceTest( + 'by reference: can add a custom time range to panel', + async ({ pageObjects }, testInfo) => { + const libraryTitle = `My by reference visualization - ${testInfo.title.replace(/\s+/g, '-')}`; + + await spaceTest.step('create dashboard and save Lens to library', async () => { + await createDashboard(pageObjects, `${DASHBOARD_NAME_PREFIX} - ${testInfo.title}`); + await addByValueLensPanel(pageObjects); + await pageObjects.dashboard.saveToLibrary(libraryTitle); + }); + + await spaceTest.step('apply custom time range and verify badge', async () => { + await applyCustomTimeRange(pageObjects, libraryTitle); + await pageObjects.dashboard.expectTimeRangeBadgeExists(); + await pageObjects.dashboard.expectEmptyPlaceholderVisible(); + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + await pageObjects.dashboard.clickQuickSave(); + }); + } + ); + + spaceTest( + 'by reference: can remove a custom time range from a panel', + async ({ pageObjects }, testInfo) => { + const libraryTitle = `My by reference visualization - ${testInfo.title.replace(/\s+/g, '-')}`; + + await spaceTest.step('create dashboard and save Lens to library', async () => { + await createDashboard(pageObjects, `${DASHBOARD_NAME_PREFIX} - ${testInfo.title}`); + await addByValueLensPanel(pageObjects); + await pageObjects.dashboard.saveToLibrary(libraryTitle); + }); + + await spaceTest.step('apply custom time range', async () => { + await applyCustomTimeRange(pageObjects, libraryTitle); + await pageObjects.dashboard.expectTimeRangeBadgeExists(); + }); + + await spaceTest.step('remove custom time range and verify panel', async () => { + await removeCustomTimeRange(pageObjects); + await pageObjects.dashboard.expectTimeRangeBadgeMissing(); + await pageObjects.dashboard.expectXYVisChartVisible(); + expect(await pageObjects.dashboard.getPanelCount()).toBe(1); + }); + } + ); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/panel_titles.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/panel_titles.spec.ts new file mode 100644 index 0000000000000..f90fe7e084edc --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/panel_titles.spec.ts @@ -0,0 +1,296 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { spaceTest, expect, tags } from '@kbn/scout'; +import type { PageObjects } from '@kbn/scout'; +import { + LENS_BASIC_DATA_VIEW, + LENS_BASIC_KIBANA_ARCHIVE, + LENS_BASIC_TIME_RANGE, + LENS_BASIC_TITLE, +} from '../constants'; + +const PANEL_TITLES_MARKDOWN_CONTENT = 'Panel title test markdown'; +const PANEL_TITLES_LIBRARY_DESCRIPTION = 'Vis library description'; +const PANEL_TITLES_CUSTOM_TITLE = 'Custom title'; +const PANEL_TITLES_CUSTOM_TITLE_CAPS = 'Custom Title'; +const PANEL_TITLES_CUSTOM_DESCRIPTION = 'Custom description'; + +spaceTest.describe('Panel titles (dashboard)', { tag: tags.DEPLOYMENT_AGNOSTIC }, () => { + let lensSavedObjectId = ''; + + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + const importedObjects = await scoutSpace.savedObjects.load(LENS_BASIC_KIBANA_ARCHIVE); + const lensObject = importedObjects.find( + (savedObject) => savedObject.type === 'lens' && savedObject.title === LENS_BASIC_TITLE + ); + expect( + lensObject, + `Lens saved object "${LENS_BASIC_TITLE}" was not found in ${LENS_BASIC_KIBANA_ARCHIVE}` + ).toBeTruthy(); + lensSavedObjectId = lensObject!.id; + + await scoutSpace.uiSettings.setDefaultIndex(LENS_BASIC_DATA_VIEW); + await scoutSpace.uiSettings.setDefaultTime(LENS_BASIC_TIME_RANGE); + }); + + spaceTest.beforeEach(async ({ browserAuth, pageObjects }) => { + await browserAuth.loginAsPrivilegedUser(); + await pageObjects.dashboard.goto(); + await pageObjects.dashboard.openNewDashboard(); + await pageObjects.dashboard.waitForRenderComplete(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.uiSettings.unset('defaultIndex', 'timepicker:timeDefaults'); + await scoutSpace.savedObjects.cleanStandardList(); + }); + + const getClonedPanelTitle = async (pageObjects: PageObjects) => { + const titles = await pageObjects.dashboard.getPanelTitles(); + const clonedTitle = titles.find((title) => title.includes('(copy)')); + expect(clonedTitle, 'Cloned panel title not found after duplicating').toBeTruthy(); + return clonedTitle!; + }; + + spaceTest('new panel by value has empty title', async ({ pageObjects }) => { + await spaceTest.step('add markdown panel by value', async () => { + await pageObjects.dashboard.addMarkdownPanel(PANEL_TITLES_MARKDOWN_CONTENT); + }); + + await spaceTest.step('verify title is empty', async () => { + await expect(pageObjects.dashboard.getPanelTitlesLocator()).toHaveCount(0); + }); + }); + + spaceTest('blank title clears unsaved changes', async ({ page, pageObjects }) => { + await spaceTest.step('add markdown panel by value', async () => { + await pageObjects.dashboard.addMarkdownPanel(PANEL_TITLES_MARKDOWN_CONTENT); + }); + + await spaceTest.step('set blank title and save', async () => { + await pageObjects.dashboard.openCustomizePanel(); + await pageObjects.dashboard.setCustomPanelTitle(''); + await pageObjects.dashboard.saveCustomizePanel(); + await pageObjects.dashboard.clearUnsavedChanges(); + }); + + await spaceTest.step('verify title is empty and badge is gone', async () => { + await expect(pageObjects.dashboard.getPanelTitlesLocator()).toHaveCount(0); + await expect(page.testSubj.locator('dashboardUnsavedChangesBadge')).toHaveCount(0); + }); + }); + + spaceTest('custom title causes unsaved changes and saving clears it', async ({ pageObjects }) => { + await spaceTest.step('add markdown panel by value', async () => { + await pageObjects.dashboard.addMarkdownPanel(PANEL_TITLES_MARKDOWN_CONTENT); + }); + + await spaceTest.step('set custom title and save', async () => { + await pageObjects.dashboard.openCustomizePanel(); + await pageObjects.dashboard.setCustomPanelTitle(PANEL_TITLES_CUSTOM_TITLE); + await pageObjects.dashboard.saveCustomizePanel(); + }); + + await spaceTest.step('verify title and clear unsaved changes', async () => { + await expect(pageObjects.dashboard.getPanelTitlesLocator()).toHaveText( + PANEL_TITLES_CUSTOM_TITLE + ); + await pageObjects.dashboard.clearUnsavedChanges(); + }); + }); + + spaceTest('reset title is hidden on a by value panel', async ({ pageObjects }) => { + await spaceTest.step('add markdown panel by value', async () => { + await pageObjects.dashboard.addMarkdownPanel(PANEL_TITLES_MARKDOWN_CONTENT); + }); + + await spaceTest.step('set custom title', async () => { + await pageObjects.dashboard.openCustomizePanel(); + await pageObjects.dashboard.setCustomPanelTitle(PANEL_TITLES_CUSTOM_TITLE); + await pageObjects.dashboard.saveCustomizePanel(); + }); + + await spaceTest.step('verify reset title button is missing', async () => { + await pageObjects.dashboard.openCustomizePanel(PANEL_TITLES_CUSTOM_TITLE); + await expect(pageObjects.dashboard.getResetCustomPanelTitleButton()).toHaveCount(0); + await pageObjects.dashboard.closeCustomizePanel(); + }); + }); + + spaceTest('reset description is hidden on a by value panel', async ({ pageObjects }) => { + await spaceTest.step('add markdown panel by value', async () => { + await pageObjects.dashboard.addMarkdownPanel(PANEL_TITLES_MARKDOWN_CONTENT); + }); + + await spaceTest.step('set custom description', async () => { + await pageObjects.dashboard.openCustomizePanel(); + await pageObjects.dashboard.setCustomPanelDescription(PANEL_TITLES_CUSTOM_DESCRIPTION); + await pageObjects.dashboard.saveCustomizePanel(); + }); + + await spaceTest.step('verify reset description button is missing', async () => { + await pageObjects.dashboard.openCustomizePanel(); + await expect(pageObjects.dashboard.getResetCustomPanelDescriptionButton()).toHaveCount(0); + await pageObjects.dashboard.closeCustomizePanel(); + }); + }); + + spaceTest( + 'linking a by value panel with a custom title to the library overwrites the custom title', + async ({ pageObjects }, testInfo) => { + const libraryTitle = `Vis Library Title - ${testInfo.title.replace(/\s+/g, '-')}`; + + await spaceTest.step('add lens panel from library and clone to by value', async () => { + await pageObjects.dashboard.addLens(LENS_BASIC_TITLE); + await pageObjects.dashboard.clonePanel(LENS_BASIC_TITLE); + }); + + await spaceTest.step('set custom title and save to library', async () => { + const byValueTitle = await getClonedPanelTitle(pageObjects); + + await pageObjects.dashboard.openCustomizePanel(byValueTitle); + await pageObjects.dashboard.setCustomPanelTitle(PANEL_TITLES_CUSTOM_TITLE); + await pageObjects.dashboard.saveCustomizePanel(); + await pageObjects.dashboard.saveToLibrary(libraryTitle, PANEL_TITLES_CUSTOM_TITLE); + }); + + await spaceTest.step('verify panel title matches library title', async () => { + await expect + .poll(async () => await pageObjects.dashboard.getPanelTitles()) + .toContain(libraryTitle); + }); + } + ); + + spaceTest( + 'resetting title on a by reference panel uses the library title', + async ({ pageObjects }) => { + await spaceTest.step('add lens panel from library', async () => { + await pageObjects.dashboard.addLens(LENS_BASIC_TITLE); + }); + + await spaceTest.step('set custom title and reset', async () => { + await pageObjects.dashboard.openCustomizePanel(LENS_BASIC_TITLE); + await pageObjects.dashboard.setCustomPanelTitle(PANEL_TITLES_CUSTOM_TITLE_CAPS); + await pageObjects.dashboard.saveCustomizePanel(); + + await pageObjects.dashboard.openCustomizePanel(PANEL_TITLES_CUSTOM_TITLE_CAPS); + await pageObjects.dashboard.resetCustomPanelTitle(); + await pageObjects.dashboard.saveCustomizePanel(); + }); + + await spaceTest.step('verify title reset to library title', async () => { + await pageObjects.dashboard.openCustomizePanel(LENS_BASIC_TITLE); + const panelTitle = await pageObjects.dashboard.getCustomPanelTitle(); + expect(panelTitle).toBe(LENS_BASIC_TITLE); + await pageObjects.dashboard.closeCustomizePanel(); + }); + } + ); + + spaceTest( + 'resetting description on a by reference panel uses the library description', + async ({ pageObjects, scoutSpace, kbnClient }) => { + await spaceTest.step('update library description via API', async () => { + await kbnClient.request({ + method: 'PUT', + path: `/s/${scoutSpace.id}/api/saved_objects/lens/${lensSavedObjectId}`, + body: { + attributes: { description: PANEL_TITLES_LIBRARY_DESCRIPTION }, + }, + }); + }); + + await spaceTest.step('add lens panel from library', async () => { + await pageObjects.dashboard.addLens(LENS_BASIC_TITLE); + }); + + await spaceTest.step('set custom description and reset', async () => { + await pageObjects.dashboard.openCustomizePanel(LENS_BASIC_TITLE); + await pageObjects.dashboard.setCustomPanelDescription(PANEL_TITLES_CUSTOM_DESCRIPTION); + await pageObjects.dashboard.saveCustomizePanel(); + + await pageObjects.dashboard.openCustomizePanel(LENS_BASIC_TITLE); + await pageObjects.dashboard.resetCustomPanelDescription(); + await pageObjects.dashboard.saveCustomizePanel(); + }); + + await spaceTest.step('verify description reset to library description', async () => { + await pageObjects.dashboard.openCustomizePanel(LENS_BASIC_TITLE); + const panelDescription = await pageObjects.dashboard.getCustomPanelDescription(); + expect(panelDescription).toBe(PANEL_TITLES_LIBRARY_DESCRIPTION); + await pageObjects.dashboard.closeCustomizePanel(); + }); + } + ); + + spaceTest( + 'unlinking a by reference panel with a custom title keeps the title', + async ({ pageObjects }) => { + await spaceTest.step('add lens panel from library', async () => { + await pageObjects.dashboard.addLens(LENS_BASIC_TITLE); + }); + + await spaceTest.step('set custom title and unlink', async () => { + await pageObjects.dashboard.openCustomizePanel(LENS_BASIC_TITLE); + await pageObjects.dashboard.setCustomPanelTitle(PANEL_TITLES_CUSTOM_TITLE); + await pageObjects.dashboard.saveCustomizePanel(); + await pageObjects.dashboard.unlinkFromLibrary(PANEL_TITLES_CUSTOM_TITLE); + }); + + await spaceTest.step('verify panel title is preserved', async () => { + const [panelTitle] = await pageObjects.dashboard.getPanelTitles(); + expect(panelTitle).toBe('Custom title'); + }); + } + ); + + spaceTest( + 'linking a by value panel with a blank title sets the panel title to the library title', + async ({ pageObjects }, testInfo) => { + const libraryTitle = `Vis Library Title - ${testInfo.title.replace(/\s+/g, '-')}`; + + await spaceTest.step('add lens panel from library and clone to by value', async () => { + await pageObjects.dashboard.addLens(LENS_BASIC_TITLE); + await pageObjects.dashboard.clonePanel(LENS_BASIC_TITLE); + await pageObjects.dashboard.removePanel(LENS_BASIC_TITLE); + }); + + await spaceTest.step('set blank title and save to library', async () => { + const clonedTitle = await getClonedPanelTitle(pageObjects); + await pageObjects.dashboard.openCustomizePanel(clonedTitle); + await pageObjects.dashboard.setCustomPanelTitle(''); + await pageObjects.dashboard.saveCustomizePanel(); + await pageObjects.dashboard.saveToLibrary(libraryTitle); + }); + + await spaceTest.step('verify panel title matches library title', async () => { + const [panelTitle] = await pageObjects.dashboard.getPanelTitles(); + expect(panelTitle).toBe(libraryTitle); + }); + } + ); + + spaceTest( + 'unlinking a by reference panel without a custom title keeps the library title', + async ({ pageObjects }) => { + await spaceTest.step('add lens panel from library', async () => { + await pageObjects.dashboard.addLens(LENS_BASIC_TITLE); + }); + + await spaceTest.step('unlink panel and verify title', async () => { + await pageObjects.dashboard.unlinkFromLibrary(LENS_BASIC_TITLE); + const [panelTitle] = await pageObjects.dashboard.getPanelTitles(); + expect(panelTitle).toBe(LENS_BASIC_TITLE); + }); + } + ); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/sync_colors.spec.ts b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/sync_colors.spec.ts new file mode 100644 index 0000000000000..2bf4d68fd698f --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/ui/parallel_tests/sync_colors.spec.ts @@ -0,0 +1,189 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import type { DebugState } from '@elastic/charts'; +import { spaceTest, expect, tags } from '@kbn/scout'; +import type { PageObjects } from '@kbn/scout'; +import type { ScoutPage } from '@kbn/scout'; +import { + LENS_BASIC_DATA_VIEW, + LENS_BASIC_KIBANA_ARCHIVE, + LENS_BASIC_TIME_RANGE, +} from '../constants'; + +const normalizeKey = (value: string) => { + const trimmed = value.trim(); + const withoutInstruction = trimmed.split(';')[0].split(' Click')[0]; + const withoutSuffix = withoutInstruction.split(' - ')[0]; + const lastSegment = withoutSuffix.split(':').pop() ?? withoutSuffix; + return lastSegment.replace(/[^A-Za-z0-9 _.-]/g, '').trim(); +}; + +const getColorMapping = (debugState: DebugState | null) => { + const colorMapping: Record = {}; + if (debugState?.legend?.items?.length) { + debugState.legend.items.forEach(({ name, color }) => { + colorMapping[normalizeKey(name)] = color; + }); + return colorMapping; + } + debugState?.bars?.forEach(({ name, color }) => { + colorMapping[normalizeKey(name)] = color; + }); + return colorMapping; +}; + +declare global { + interface Window { + _echDebugStateFlag?: boolean; + } +} + +const getChartDebugState = async (page: ScoutPage, panelIndex: number) => { + await expect.poll(() => page.locator('.echChart').count()).toBeGreaterThan(panelIndex); + const charts = await page.locator('.echChart').all(); + const chart = charts[panelIndex]; + if (!chart) { + throw new Error(`Chart panel index ${panelIndex} not found`); + } + const status = chart.locator('.echChartStatus'); + let debugState: string | null = null; + await expect + .poll(async () => { + debugState = await status.getAttribute('data-ech-debug-state'); + return debugState; + }) + .not.toBeNull(); + return debugState ? (JSON.parse(debugState) as DebugState) : null; +}; + +const createBaseXYCharts = async ( + pageObjects: PageObjects, + filterBar: PageObjects['filterBar'] +) => { + await pageObjects.dashboard.openNewLensPanel(); + await pageObjects.lens.configureXYDimensions({ + y: { operation: 'count' }, + split: { + operation: 'terms', + field: 'geo.src', + palette: { mode: 'legacy', id: 'default' }, + }, + }); + await pageObjects.lens.saveAndReturn(); + await pageObjects.dashboard.waitForPanelsToLoad(1); + + await pageObjects.dashboard.openNewLensPanel(); + await pageObjects.lens.configureXYDimensions({ + y: { operation: 'count' }, + split: { + operation: 'terms', + field: 'geo.src', + palette: { mode: 'legacy', id: 'default' }, + }, + }); + await filterBar.addFilter({ field: 'geo.src', operator: 'is not', value: 'CN' }); + await pageObjects.lens.saveAndReturn(); + await pageObjects.dashboard.waitForPanelsToLoad(2); +}; + +spaceTest.describe('Sync colors', { tag: tags.DEPLOYMENT_AGNOSTIC }, () => { + spaceTest.beforeAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + await scoutSpace.savedObjects.load(LENS_BASIC_KIBANA_ARCHIVE); + await scoutSpace.uiSettings.setDefaultIndex(LENS_BASIC_DATA_VIEW); + await scoutSpace.uiSettings.setDefaultTime(LENS_BASIC_TIME_RANGE); + }); + + spaceTest.beforeEach(async ({ browserAuth, page, pageObjects }) => { + await browserAuth.loginAsPrivilegedUser(); + await page.addInitScript(() => { + window._echDebugStateFlag = true; + }); + await pageObjects.dashboard.goto(); + await pageObjects.dashboard.openNewDashboard(); + }); + + spaceTest.afterAll(async ({ scoutSpace }) => { + await scoutSpace.savedObjects.cleanStandardList(); + }); + + // Investigate / fix test. FTR equivalent test was failing: see https://github.com/elastic/kibana/issues/235883 + spaceTest.skip( + 'should sync colors on dashboard for legacy default palette', + async ({ page, pageObjects, pageObjects: { filterBar } }) => { + await spaceTest.step('create xy charts with legacy palette', async () => { + await createBaseXYCharts(pageObjects, filterBar); + }); + + await spaceTest.step('enable sync colors and compare mappings', async () => { + await pageObjects.dashboard.openSettingsFlyout(); + await pageObjects.dashboard.toggleSyncColors(true); + await pageObjects.dashboard.applyDashboardSettings(); + await page.testSubj.click('querySubmitButton'); + + const getMergedColorAssignments = async () => { + const colorMappings1 = Object.entries(getColorMapping(await getChartDebugState(page, 0))); + const colorMappings2 = Object.entries(getColorMapping(await getChartDebugState(page, 1))); + const mergedColorAssignments = new Map>(); + + [...colorMappings1, ...colorMappings2].forEach(([key, color]) => { + mergedColorAssignments.set(key, mergedColorAssignments.get(key) ?? new Set()); + mergedColorAssignments.get(key)?.add(color); + }); + + return { colorMappings1, colorMappings2, mergedColorAssignments }; + }; + + await expect + .poll( + async () => { + const { mergedColorAssignments } = await getMergedColorAssignments(); + return [...mergedColorAssignments.values()].every((colors) => colors.size === 1); + }, + { timeout: 30_000 } + ) + .toBe(true); + + const { colorMappings1, colorMappings2, mergedColorAssignments } = + await getMergedColorAssignments(); + + expect(colorMappings1.length).toBeGreaterThan(0); + expect(colorMappings2).toHaveLength(colorMappings1.length); + + mergedColorAssignments.forEach((colors, key) => { + expect( + colors.size, + `Key "${key}" was assigned multiple colors: ${JSON.stringify([...colors])}` + ).toBe(1); + }); + }); + } + ); + + spaceTest('should be possible to disable color sync', async ({ page, pageObjects }) => { + await spaceTest.step('create xy charts with legacy palette', async () => { + await createBaseXYCharts(pageObjects, pageObjects.filterBar); + }); + + await spaceTest.step('disable sync colors', async () => { + await pageObjects.dashboard.openSettingsFlyout(); + await pageObjects.dashboard.toggleSyncColors(false); + await pageObjects.dashboard.applyDashboardSettings(); + }); + + await spaceTest.step('compare color mappings', async () => { + const colorMapping1 = getColorMapping(await getChartDebugState(page, 0)); + const colorMapping2 = getColorMapping(await getChartDebugState(page, 1)); + const colorsByOrder1 = Object.values(colorMapping1); + const colorsByOrder2 = Object.values(colorMapping2); + expect(colorsByOrder1).toStrictEqual(colorsByOrder2); + }); + }); +}); diff --git a/src/platform/plugins/shared/dashboard/test/scout/utils/migration_smoke_helpers.ts b/src/platform/plugins/shared/dashboard/test/scout/utils/migration_smoke_helpers.ts new file mode 100644 index 0000000000000..7625ffb17a382 --- /dev/null +++ b/src/platform/plugins/shared/dashboard/test/scout/utils/migration_smoke_helpers.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import fs from 'fs'; +import path from 'path'; +import { expect } from '@kbn/scout'; +import type { ScoutPage } from '@kbn/scout'; +import type { KbnClient } from '@kbn/test'; + +export interface ImportedSavedObject { + id: string; + type: string; + title: string; +} + +export interface DashboardPanel { + embeddableConfig?: { enhancements?: { dynamicActions?: { events?: unknown[] } } }; +} + +export const openDashboard = async (page: ScoutPage, id: string) => { + await page.gotoApp('dashboards', { hash: `/view/${id}` }); +}; + +export const findImportedSavedObjectId = ( + imported: ImportedSavedObject[], + type: string, + title: string +) => { + const savedObject = imported.find((entry) => entry.type === type && entry.title === title); + expect( + savedObject, + `Saved object "${title}" (${type}) not found in imported objects` + ).toBeTruthy(); + return savedObject!.id; +}; + +const readExportEntries = (exportPath: string) => { + const absolutePath = path.resolve(process.cwd(), exportPath); + return fs + .readFileSync(absolutePath, 'utf8') + .trim() + .split(/\r?\n\r?\n/) + .map((line) => JSON.parse(line)); +}; + +const getIndexPatternFromExport = (exportPath: string) => { + const entries = readExportEntries(exportPath); + const indexPattern = entries.find((entry) => entry.type === 'index-pattern'); + expect(indexPattern, `Index pattern not found in ${exportPath}`).toBeTruthy(); + return indexPattern as { id: string; attributes: Record }; +}; + +export const ensureIndexPatternFromExport = async ( + kbnClient: KbnClient, + spaceId: string, + exportPath: string +) => { + const indexPattern = getIndexPatternFromExport(exportPath); + try { + await kbnClient.savedObjects.delete({ + type: 'index-pattern', + id: indexPattern.id, + space: spaceId, + }); + } catch { + // Ignore missing index pattern + } + await kbnClient.request({ + method: 'POST', + path: `/s/${spaceId}/api/saved_objects/index-pattern/${indexPattern.id}`, + body: { attributes: indexPattern.attributes }, + }); +}; + +export const getDashboardPanels = async (kbnClient: KbnClient, spaceId: string, id: string) => { + const dashboard = await kbnClient.savedObjects.get<{ panelsJSON?: string }>({ + type: 'dashboard', + id, + space: spaceId, + }); + return JSON.parse(dashboard.attributes?.panelsJSON ?? '[]') as DashboardPanel[]; +}; diff --git a/src/platform/plugins/shared/dashboard/tsconfig.json b/src/platform/plugins/shared/dashboard/tsconfig.json index 40beab65caf55..ef140ad0f2b4d 100644 --- a/src/platform/plugins/shared/dashboard/tsconfig.json +++ b/src/platform/plugins/shared/dashboard/tsconfig.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "target/types" }, - "include": ["*.ts", ".storybook/**/*.ts", "common/**/*", "public/**/*", "server/**/*"], + "include": ["*.ts", ".storybook/**/*.ts", "common/**/*", "public/**/*", "server/**/*", "test/scout/**/*"], "kbn_references": [ "@kbn/core", "@kbn/inspector-plugin", @@ -92,7 +92,9 @@ "@kbn/controls-constants", "@kbn/presentation-util", "@kbn/background-search", - "@kbn/react-query" + "@kbn/react-query", + "@kbn/scout", + "@kbn/test" ], "exclude": ["target/**/*"] } diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/_async_dashboard.ts b/x-pack/platform/test/functional/apps/dashboard/group2/_async_dashboard.ts deleted file mode 100644 index 050bcca6780f5..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/_async_dashboard.ts +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import { UI_SETTINGS } from '@kbn/data-plugin/common'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const browser = getService('browser'); - const kibanaServer = getService('kibanaServer'); - const esArchiver = getService('esArchiver'); - const log = getService('log'); - const elasticChart = getService('elasticChart'); - const find = getService('find'); - const renderable = getService('renderable'); - const dashboardExpect = getService('dashboardExpect'); - const appMenu = getService('appsMenu'); - const { common, header, home, discover, dashboard, timePicker } = getPageObjects([ - 'common', - 'header', - 'home', - 'discover', - 'dashboard', - 'timePicker', - ]); - - describe('sample data dashboard', function describeIndexTests() { - before(async () => { - await esArchiver.emptyKibanaIndex(); - await common.sleep(5000); - await common.navigateToUrl('home', '/tutorial_directory/sampleData', { - useActualUrl: true, - }); - await header.waitUntilLoadingHasFinished(); - await home.addSampleDataSet('flights'); - await retry.tryForTime(10000, async () => { - const isInstalled = await home.isSampleDataSetInstalled('flights'); - expect(isInstalled).to.be(true); - }); - - // add the range of the sample data so we can pick it in the quick pick list - const SAMPLE_DATA_RANGE = `[ - { - "from": "now-30d", - "to": "now+40d", - "display": "sample data range" - }, - { - "from": "now/d", - "to": "now/d", - "display": "Today" - }, - { - "from": "now/w", - "to": "now/w", - "display": "This week" - }, - { - "from": "now-15m", - "to": "now", - "display": "Last 15 minutes" - }, - { - "from": "now-30m", - "to": "now", - "display": "Last 30 minutes" - }, - { - "from": "now-1h", - "to": "now", - "display": "Last 1 hour" - }, - { - "from": "now-24h", - "to": "now", - "display": "Last 24 hours" - }, - { - "from": "now-7d", - "to": "now", - "display": "Last 7 days" - }, - { - "from": "now-30d", - "to": "now", - "display": "Last 30 days" - }, - { - "from": "now-90d", - "to": "now", - "display": "Last 90 days" - }, - { - "from": "now-1y", - "to": "now", - "display": "Last 1 year" - } - ]`; - - await kibanaServer.uiSettings.update({ - [UI_SETTINGS.TIMEPICKER_QUICK_RANGES]: SAMPLE_DATA_RANGE, - }); - // refresh page to make sure ui settings update is picked up - await browser.refresh(); - await header.waitUntilLoadingHasFinished(); - await appMenu.clickLink('Discover'); - await discover.selectIndexPattern('Kibana Sample Data Flights'); - await timePicker.setCommonlyUsedTime('sample_data range'); - await retry.try(async function () { - const hitCount = parseInt(await discover.getHitCount(), 10); - expect(hitCount).to.be.greaterThan(0); - }); - }); - - after(async () => { - await common.navigateToUrl('home', '/tutorial_directory/sampleData', { - useActualUrl: true, - }); - await header.waitUntilLoadingHasFinished(); - await home.removeSampleDataSet('flights'); - const isInstalled = await home.isSampleDataSetInstalled('flights'); - expect(isInstalled).to.be(false); - }); - - it('should launch sample flights data set dashboard', async () => { - await dashboard.navigateToApp(); - await dashboard.loadSavedDashboard('[Flights] Global Flight Dashboard'); - await header.waitUntilLoadingHasFinished(); - await timePicker.setCommonlyUsedTime('sample_data range'); - await header.waitUntilLoadingHasFinished(); - - // check at least one visualization - await renderable.waitForRender(); - log.debug('Checking charts rendered'); - await elasticChart.waitForRenderComplete('xyVisChart'); - - await appMenu.clickLink('Discover'); - await retry.try(async function () { - const hitCount = parseInt(await discover.getHitCount(), 10); - expect(hitCount).to.be.greaterThan(0); - }); - await appMenu.clickLink('Dashboard', { - category: 'recentlyViewed', - closeCollapsibleNav: true, - }); - await header.waitUntilLoadingHasFinished(); - await renderable.waitForRender(); - log.debug('Checking charts rendered'); - await elasticChart.waitForRenderComplete('xyVisChart'); - }); - - it('toggle from Discover to Dashboard attempt 1', async () => { - await appMenu.clickLink('Discover'); - await retry.try(async function () { - const hitCount = parseInt(await discover.getHitCount(), 10); - expect(hitCount).to.be.greaterThan(0); - }); - await appMenu.clickLink('Dashboard', { - category: 'recentlyViewed', - closeCollapsibleNav: true, - }); - await header.waitUntilLoadingHasFinished(); - await renderable.waitForRender(); - log.debug('Checking charts rendered'); - await elasticChart.waitForRenderComplete('xyVisChart'); - }); - - it('toggle from Discover to Dashboard attempt 2', async () => { - await appMenu.clickLink('Discover'); - await retry.try(async function () { - const hitCount = parseInt(await discover.getHitCount(), 10); - expect(hitCount).to.be.greaterThan(0); - }); - await appMenu.clickLink('Dashboard', { - category: 'recentlyViewed', - closeCollapsibleNav: true, - }); - await header.waitUntilLoadingHasFinished(); - await renderable.waitForRender(); - log.debug('Checking charts rendered'); - await elasticChart.waitForRenderComplete('xyVisChart'); - - log.debug('Checking saved searches rendered'); - await dashboardExpect.savedSearchRowCount(10); - log.debug('Checking input controls rendered'); - await dashboardExpect.controlCount(3); - log.debug('Checking tag cloud rendered'); - await dashboardExpect.tagCloudWithValuesFound(['Sunny', 'Rain', 'Clear', 'Cloudy', 'Hail']); - log.debug('Checking vega chart rendered'); - expect(await find.existsByCssSelector('.vgaVis__view')).to.be(true); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/config.ts b/x-pack/platform/test/functional/apps/dashboard/group2/config.ts deleted file mode 100644 index 9c9957e5ff401..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/config.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { FtrConfigProviderContext } from '@kbn/test'; - -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../../../config.base.ts')); - - return { - ...functionalConfig.getAll(), - testFiles: [require.resolve('.')], - }; -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts b/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts deleted file mode 100644 index 65acc7a6abd6a..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_lens_by_value.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const { dashboard, visualize, lens, timePicker } = getPageObjects([ - 'dashboard', - 'visualize', - 'lens', - 'timePicker', - ]); - - const esArchiver = getService('esArchiver'); - const testSubjects = getService('testSubjects'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const kibanaServer = getService('kibanaServer'); - - describe('dashboard lens by value', function () { - before(async () => { - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/logstash_functional' - ); - await kibanaServer.importExport.load( - 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json' - ); - await dashboard.navigateToApp(); - await dashboard.preserveCrossAppState(); - await dashboard.clickNewDashboard(); - }); - - after(async () => { - await esArchiver.unload('x-pack/platform/test/fixtures/es_archives/logstash_functional'); - await kibanaServer.importExport.unload( - 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json' - ); - }); - - it('can add a lens panel by value', async () => { - await lens.createAndAddLensFromDashboard({}); - const newPanelCount = await dashboard.getPanelCount(); - expect(newPanelCount).to.eql(1); - }); - - it('edits to a by value lens panel are properly applied', async () => { - await dashboard.waitForRenderComplete(); - await dashboardPanelActions.navigateToEditorFromFlyout(); - await lens.switchToVisualization('pie'); - await lens.saveAndReturn(); - await dashboard.waitForRenderComplete(); - - const partitionVisExists = await testSubjects.exists('partitionVisChart'); - expect(partitionVisExists).to.be(true); - }); - - it('editing and saving a lens by value panel retains number of panels', async () => { - const originalPanelCount = await dashboard.getPanelCount(); - await dashboard.waitForRenderComplete(); - await dashboardPanelActions.navigateToEditorFromFlyout(); - await lens.switchToVisualization('treemap'); - await lens.saveAndReturn(); - await dashboard.waitForRenderComplete(); - const newPanelCount = await dashboard.getPanelCount(); - expect(newPanelCount).to.eql(originalPanelCount); - }); - - it('updates panel on dashboard when a by value panel is saved to library', async () => { - const newTitle = 'look out library, here I come!'; - const originalPanelCount = await dashboard.getPanelCount(); - await dashboard.waitForRenderComplete(); - await dashboardPanelActions.navigateToEditorFromFlyout(); - await lens.save(newTitle, false, true); - await dashboard.waitForRenderComplete(); - const newPanelCount = await dashboard.getPanelCount(); - expect(newPanelCount).to.eql(originalPanelCount); - const titles = await dashboard.getPanelTitles(); - expect(titles.indexOf(newTitle)).to.not.be(-1); - }); - - it('is no longer linked to a dashboard after visiting the visualize listing page', async () => { - await visualize.navigateToNewVisualization(); - await visualize.clickLensWidget(); - await timePicker.ensureHiddenNoDataPopover(); - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'average', - field: 'bytes', - }); - await lens.waitForVisualization('xyVisChart'); - await lens.notLinkedToOriginatingApp(); - - // return to origin should not be present in save modal - await testSubjects.click('lnsApp_saveButton'); - const redirectToOriginCheckboxExists = await testSubjects.exists('returnToOriginModeSwitch'); - expect(redirectToOriginCheckboxExists).to.be(false); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts b/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts deleted file mode 100644 index cd64f33225578..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_maps_by_value.ts +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getPageObjects, getService }: FtrProviderContext) { - const { dashboard, maps, timeToVisualize } = getPageObjects([ - 'dashboard', - 'maps', - 'timeToVisualize', - ]); - - const log = getService('log'); - const esArchiver = getService('esArchiver'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const testSubjects = getService('testSubjects'); - const dashboardAddPanel = getService('dashboardAddPanel'); - const kibanaServer = getService('kibanaServer'); - - let mapCounter = 0; - - async function createAndAddMapByValue() { - log.debug(`createAndAddMapByValue`); - const inViewMode = await dashboard.getIsInViewMode(); - if (inViewMode) { - await dashboard.switchToEditMode(); - } - await dashboardAddPanel.clickAddMapPanel(); - await maps.clickSaveAndReturnButton(); - } - - async function editByValueMap(saveToLibrary = false, saveToDashboard = true) { - const inViewMode = await dashboard.getIsInViewMode(); - if (inViewMode) { - await dashboard.switchToEditMode(); - } - - await dashboardPanelActions.clickEdit(); - await maps.clickAddLayer(); - await maps.selectLayerGroupCard(); - - await testSubjects.click('importFileButton'); - - if (saveToLibrary) { - await testSubjects.click('mapSaveButton'); - await timeToVisualize.ensureSaveModalIsOpen; - - await timeToVisualize.saveFromModal(`my map ${mapCounter++}`, { - redirectToOrigin: saveToDashboard, - }); - - if (!saveToDashboard) { - await dashboard.navigateToAppFromAppsMenu(); - } - } else { - await maps.clickSaveAndReturnButton(); - } - - await dashboard.waitForRenderComplete(); - } - - async function createNewDashboard() { - await dashboard.navigateToApp(); - await dashboard.preserveCrossAppState(); - await dashboard.clickNewDashboard(); - } - - describe('dashboard maps by value', function () { - before(async () => { - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/logstash_functional' - ); - await kibanaServer.importExport.load( - 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json' - ); - }); - - after(async () => { - await esArchiver.unload('x-pack/platform/test/fixtures/es_archives/logstash_functional'); - await kibanaServer.importExport.unload( - 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json' - ); - await kibanaServer.savedObjects.cleanStandardList(); - }); - - describe('adding a map by value', () => { - it('can add a map by value', async () => { - await createNewDashboard(); - await createAndAddMapByValue(); - const newPanelCount = await dashboard.getPanelCount(); - expect(newPanelCount).to.eql(1); - }); - }); - - describe('editing a map by value', () => { - before(async () => { - await createNewDashboard(); - await createAndAddMapByValue(); - await editByValueMap(); - }); - - it('retains the same number of panels', async () => { - const panelCount = await dashboard.getPanelCount(); - expect(panelCount).to.equal(1); - }); - - it('updates the panel on return', async () => { - const hasLayer = await maps.doesLayerExist('Layer group'); - expect(hasLayer).to.be(true); - }); - }); - - describe('editing a map and adding to map library', () => { - beforeEach(async () => { - await createNewDashboard(); - await createAndAddMapByValue(); - }); - - it('updates the existing panel when adding to dashboard', async () => { - await editByValueMap(true); - - const hasLayer = await maps.doesLayerExist('Layer group'); - expect(hasLayer).to.be(true); - }); - - it('does not update the panel when only saving to library', async () => { - await editByValueMap(true, false); - - const hasLayer = await maps.doesLayerExist('Layer group'); - expect(hasLayer).to.be(false); - }); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_panel_listing.ts b/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_panel_listing.ts deleted file mode 100644 index 7fb52d813e90c..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_panel_listing.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; - -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const { dashboard } = getPageObjects(['dashboard']); - const testSubjects = getService('testSubjects'); - const kibanaServer = getService('kibanaServer'); - const dashboardAddPanel = getService('dashboardAddPanel'); - - describe('dashboard panel listing', function () { - this.tags('skipFIPS'); - before(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - await kibanaServer.importExport.load( - 'src/platform/test/functional/fixtures/kbn_archiver/dashboard/current/kibana' - ); - await kibanaServer.uiSettings.replace({ - defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', - }); - }); - - after(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - }); - - it('renders a panel with predefined order of panel groups and panels', async () => { - await dashboard.navigateToApp(); - await dashboard.clickNewDashboard(); - await dashboard.switchToEditMode(); - - await dashboardAddPanel.openAddPanelFlyout(); - - const panelSelectionList = await testSubjects.find('dashboardPanelSelectionList'); - - const panelGroupByOrder = new Map(); - - const panelGroups = await panelSelectionList.findAllByCssSelector( - '[data-test-subj*="dashboardEditorMenu-"]' - ); - - const panelTypes = await panelSelectionList.findAllByCssSelector('li'); - - for (let i = 0; i < panelGroups.length; i++) { - const panelGroup = panelGroups[i]; - const order = await panelGroup.getAttribute('data-group-sort-order'); - // @ts-ignore - const [, panelGroupTitle] = (await panelGroup.getAttribute('data-test-subj'))?.match( - /dashboardEditorMenu-(.*)/ - ); - - panelGroupByOrder.set(order, panelGroupTitle); - } - - expect(panelGroupByOrder.size).to.eql(4); - - expect([...panelGroupByOrder.values()]).to.eql([ - 'visualizationsGroup', - 'annotation-and-navigationGroup', - 'mlGroup', - 'observabilityGroup', - ]); - - // Any changes to the number of panels needs to be audited by @elastic/kibana-presentation - expect(panelTypes.length).to.eql(21); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts b/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts deleted file mode 100644 index 0878b7c454381..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/dashboard_search_by_value.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const dataGrid = getService('dataGrid'); - const dashboardAddPanel = getService('dashboardAddPanel'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const filterBar = getService('filterBar'); - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const { common, dashboard, header } = getPageObjects(['common', 'dashboard', 'header']); - - describe('saved searches by value', () => { - before(async () => { - await esArchiver.loadIfNeeded( - 'src/platform/test/functional/fixtures/es_archiver/logstash_functional' - ); - await esArchiver.loadIfNeeded( - 'src/platform/test/functional/fixtures/es_archiver/dashboard/current/data' - ); - await kibanaServer.savedObjects.cleanStandardList(); - await kibanaServer.importExport.load( - 'src/platform/test/functional/fixtures/kbn_archiver/dashboard/current/kibana' - ); - await kibanaServer.uiSettings.replace({ - defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', - }); - await common.setTime({ - from: 'Sep 22, 2015 @ 00:00:00.000', - to: 'Sep 23, 2015 @ 00:00:00.000', - }); - }); - - after(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - await common.unsetTime(); - }); - - beforeEach(async () => { - await dashboard.navigateToApp(); - await filterBar.ensureFieldEditorModalIsClosed(); - await dashboard.gotoDashboardLandingPage(); - await dashboard.clickNewDashboard(); - }); - - const addSearchEmbeddableToDashboard = async () => { - await dashboardAddPanel.addSavedSearch('Rendering-Test:-saved-search'); - await header.waitUntilLoadingHasFinished(); - await dashboard.waitForRenderComplete(); - const rows = await dataGrid.getDocTableRows(); - expect(rows.length).to.be.above(0); - }; - - it('should allow cloning a by ref saved search embeddable to a by value embeddable', async () => { - await addSearchEmbeddableToDashboard(); - let titles = await dashboard.getPanelTitles(); - expect(titles.length).to.be(1); - await dashboardPanelActions.expectLinkedToLibrary(titles[0]); - await dashboardPanelActions.clonePanel(titles[0]); - await header.waitUntilLoadingHasFinished(); - await dashboard.waitForRenderComplete(); - titles = await dashboard.getPanelTitles(); - expect(titles.length).to.be(2); - await dashboardPanelActions.expectLinkedToLibrary(titles[0]); - await dashboardPanelActions.expectNotLinkedToLibrary(titles[1]); - }); - - it('should allow unlinking a by ref saved search embeddable from library', async () => { - await addSearchEmbeddableToDashboard(); - let titles = await dashboard.getPanelTitles(); - expect(titles.length).to.be(1); - await dashboardPanelActions.expectLinkedToLibrary(titles[0]); - await dashboardPanelActions.unlinkFromLibrary(titles[0]); - titles = await dashboard.getPanelTitles(); - expect(titles.length).to.be(1); - await dashboardPanelActions.expectNotLinkedToLibrary(titles[0]); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/index.ts b/x-pack/platform/test/functional/apps/dashboard/group2/index.ts deleted file mode 100644 index 229667a958391..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ loadTestFile }: FtrProviderContext) { - describe('dashboard', function () { - loadTestFile(require.resolve('./sync_colors')); - loadTestFile(require.resolve('./_async_dashboard')); - loadTestFile(require.resolve('./dashboard_lens_by_value')); - loadTestFile(require.resolve('./dashboard_maps_by_value')); - loadTestFile(require.resolve('./dashboard_search_by_value')); - loadTestFile(require.resolve('./dashboard_panel_listing')); - loadTestFile(require.resolve('./panel_titles')); - loadTestFile(require.resolve('./panel_time_range')); - - loadTestFile(require.resolve('./migration_smoke_tests/lens_migration_smoke_test')); - loadTestFile(require.resolve('./migration_smoke_tests/controls_migration_smoke_test')); - loadTestFile(require.resolve('./migration_smoke_tests/visualize_migration_smoke_test')); - loadTestFile(require.resolve('./migration_smoke_tests/tsvb_migration_smoke_test')); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts b/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts deleted file mode 100644 index a3dc7893c03dc..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* - * This test imports a dashboard saved with controls from 8.0.0, because that is the earliest version - * with the dashboard controls integration in place. - */ - -import expect from '@kbn/expect'; -import path from 'path'; -import type { FtrProviderContext } from '../../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const retry = getService('retry'); - const pieChart = getService('pieChart'); - const elasticChart = getService('elasticChart'); - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); - const queryBar = getService('queryBar'); - const browser = getService('browser'); - - const { settings, savedObjects, dashboard, dashboardControls } = getPageObjects([ - 'settings', - 'dashboard', - 'savedObjects', - 'dashboardControls', - ]); - - describe('Export import saved objects between versions', () => { - before(async () => { - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/getting_started/shakespeare' - ); - await kibanaServer.uiSettings.replace({}); - await settings.navigateTo(); - await settings.clickKibanaSavedObjects(); - await savedObjects.importFile( - path.join(__dirname, 'exports', 'controls_dashboard_migration_test_8_0_0.ndjson') - ); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/platform/test/fixtures/es_archives/getting_started/shakespeare' - ); - await kibanaServer.savedObjects.cleanStandardList(); - }); - - it('should be able to import dashboard with controls from 8.0.0', async () => { - // this will catch cases where there is an error in the migrations. - await savedObjects.checkImportSucceeded(); - await savedObjects.clickImportDone(); - }); - - it('should render all panels on the dashboard', async () => { - await dashboard.navigateToApp(); - await dashboard.loadSavedDashboard('[8.0.0] Controls Dashboard'); - - // dashboard should load properly - await dashboard.expectOnDashboard('[8.0.0] Controls Dashboard'); - - // Refresh the dashboard to clear any stale search sessions from migration - await browser.refresh(); - await dashboard.expectOnDashboard('[8.0.0] Controls Dashboard'); - await dashboard.waitForRenderComplete(); - - // There should be 0 error embeddables on the dashboard - const errorEmbeddables = await testSubjects.findAll('embeddableStackError'); - expect(errorEmbeddables.length).to.be(0); - - // There should be 2 controls on the dashboard and no errors on the controls - expect(await dashboardControls.getControlsCount()).to.be(2); - await testSubjects.missingOrFail('control-frame-error'); - }); - - it('loads all controls from the saved dashboard', async () => { - expect(await dashboardControls.getControlsCount()).to.be(2); - expect(await dashboardControls.getAllControlTitles()).to.eql(['Speaker Name', 'Play Name']); - - const ids = await dashboardControls.getAllControlIds(); - - await dashboardControls.optionsListOpenPopover(ids[0]); - await retry.try(async () => { - expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(10); - }); - await dashboardControls.optionsListEnsurePopoverIsClosed(ids[0]); - - await dashboardControls.optionsListOpenPopover(ids[1]); - await retry.try(async () => { - // the second control should only have 5 available options because the previous control has HAMLET ROMEO JULIET and BRUTUS selected - expect(await dashboardControls.optionsListPopoverGetAvailableOptionsCount()).to.be(5); - }); - await dashboardControls.optionsListEnsurePopoverIsClosed(ids[1]); - }); - - it('applies default selected options list options to control', async () => { - const controlIds = await dashboardControls.getAllControlIds(); - const selectionString = await dashboardControls.optionsListGetSelectionsString(controlIds[0]); - expect(selectionString).to.be('HAMLET, ROMEO, JULIET, BRUTUS'); - }); - - it('applies default selected options list options to dashboard', async () => { - // because 4 selections are made on the control, the pie chart should only show 4 slices. - await elasticChart.setNewChartUiDebugFlag(); - await queryBar.submitQuery(); - await pieChart.expectPieSliceCountEsCharts(4); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts b/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts deleted file mode 100644 index 6e522a245a8b4..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/lens_migration_smoke_test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* This test is importing saved objects from 7.13.0 to 8.0 and the backported version - * will import from 6.8.x to 7.x.x - */ - -import expect from '@kbn/expect'; -import path from 'path'; -import type { FtrProviderContext } from '../../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions'); - - const { settings, savedObjects, dashboard } = getPageObjects([ - 'settings', - 'savedObjects', - 'dashboard', - ]); - - describe('Lens - Export import saved objects between versions', () => { - before(async () => { - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/getting_started/shakespeare' - ); - await kibanaServer.uiSettings.replace({}); - await settings.navigateTo(); - await settings.clickKibanaSavedObjects(); - await savedObjects.importFile( - path.join(__dirname, 'exports', 'lens_dashboard_migration_test_7_12_1.ndjson') - ); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/platform/test/fixtures/es_archives/getting_started/shakespeare' - ); - await kibanaServer.savedObjects.cleanStandardList(); - }); - - it('should be able to import dashboard with various Lens panels from 7.12.1', async () => { - // this will catch cases where there is an error in the migrations. - await savedObjects.checkImportSucceeded(); - await savedObjects.clickImportDone(); - }); - - it('should render all panels on the dashboard', async () => { - await dashboard.navigateToApp(); - await dashboard.loadSavedDashboard('[7.12.1] Lens By Value Test Dashboard'); - - // dashboard should load properly - await dashboard.expectOnDashboard('[7.12.1] Lens By Value Test Dashboard'); - await dashboard.waitForRenderComplete(); - - // There should be 0 error embeddables on the dashboard - const errorEmbeddables = await testSubjects.findAll('embeddableStackError', 500); - expect(errorEmbeddables.length).to.be(0); - }); - - it('should show the edit action for all panels', async () => { - await dashboard.switchToEditMode(); - - // All panels should be editable. This will catch cases where an error does not create an error embeddable. - const panelTitles = await dashboard.getPanelTitles(); - for (const title of panelTitles) { - await dashboardPanelActions.expectExistsEditPanelAction(title); - } - }); - - it('should retain all panel drilldowns from 7.12.1', async () => { - // Both panels configured with drilldowns in 7.12.1 should still have drilldowns. - const totalPanels = await dashboard.getPanelCount(); - let panelsWithDrilldowns = 0; - for (let panelIndex = 0; panelIndex < totalPanels; panelIndex++) { - if ((await dashboardDrilldownPanelActions.getPanelDrilldownCount(panelIndex)) === 1) { - panelsWithDrilldowns++; - } - } - expect(panelsWithDrilldowns).to.be(2); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts b/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts deleted file mode 100644 index 2cd1313fe4993..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/tsvb_migration_smoke_test.ts +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import path from 'path'; -import type { FtrProviderContext } from '../../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions'); - - const { settings, savedObjects, dashboard } = getPageObjects([ - 'settings', - 'savedObjects', - 'dashboard', - ]); - - describe('TSVB - Export import saved objects between versions', () => { - describe('From 7.12.1', () => { - before(async () => { - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/logstash_functional' - ); - await kibanaServer.uiSettings.replace({}); - await settings.navigateTo(); - await settings.clickKibanaSavedObjects(); - await savedObjects.importFile( - path.join(__dirname, 'exports', 'tsvb_dashboard_migration_test_7_12_1.ndjson') - ); - }); - - it('should be able to import dashboard with TSVB panels from 7.12.1', async () => { - // this will catch cases where there is an error in the migrations. - await savedObjects.checkImportSucceeded(); - await savedObjects.clickImportDone(); - }); - - it('should render all panels on the dashboard', async () => { - await dashboard.navigateToApp(); - await dashboard.loadSavedDashboard('TSVB Index Pattern Smoke Test'); - - // dashboard should load properly - await dashboard.expectOnDashboard('TSVB Index Pattern Smoke Test'); - await dashboard.waitForRenderComplete(); - - // There should be 0 error embeddables on the dashboard - const errorEmbeddables = await testSubjects.findAll('embeddableStackError'); - expect(errorEmbeddables.length).to.be(0); - }); - - it('should show the edit action for all panels', async () => { - await dashboard.switchToEditMode(); - - // All panels should be editable. This will catch cases where an error does not create an error embeddable. - const panelTitles = await dashboard.getPanelTitles(); - for (const title of panelTitles) { - await dashboardPanelActions.expectExistsEditPanelAction(title); - } - }); - - it('should retain all panel drilldowns from 7.12.1', async () => { - // Both panels configured with drilldowns in 7.12.1 should still have drilldowns. - const totalPanels = await dashboard.getPanelCount(); - let panelsWithDrilldowns = 0; - let drilldownCount = 0; - for (let panelIndex = 0; panelIndex < totalPanels; panelIndex++) { - const panelDrilldownCount = await dashboardDrilldownPanelActions.getPanelDrilldownCount( - panelIndex - ); - if (panelDrilldownCount >= 1) { - panelsWithDrilldowns++; - } - - drilldownCount += await dashboardDrilldownPanelActions.getPanelDrilldownCount(panelIndex); - } - expect(panelsWithDrilldowns).to.be(2); - expect(drilldownCount).to.be(3); - }); - - after(async () => { - await esArchiver.unload('x-pack/platform/test/fixtures/es_archives/logstash_functional'); - await kibanaServer.savedObjects.cleanStandardList(); - }); - }); - - describe('from 7.13.3', () => { - before(async () => { - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/logstash_functional' - ); - await kibanaServer.uiSettings.replace({}); - await settings.navigateTo(); - await settings.clickKibanaSavedObjects(); - await savedObjects.importFile( - path.join(__dirname, 'exports', 'tsvb_dashboard_migration_test_7_13_3.ndjson') - ); - }); - - it('should be able to import dashboard with TSVB panels from 7.13.3', async () => { - // this will catch cases where there is an error in the migrations. - await savedObjects.checkImportSucceeded(); - await savedObjects.clickImportDone(); - }); - - it('should render all panels on the dashboard', async () => { - await dashboard.navigateToApp(); - await dashboard.loadSavedDashboard('TSVB 7.13.3'); - - // dashboard should load properly - await dashboard.expectOnDashboard('TSVB 7.13.3'); - await dashboard.waitForRenderComplete(); - - // There should be 0 error embeddables on the dashboard - const errorEmbeddables = await testSubjects.findAll('embeddableStackError'); - expect(errorEmbeddables.length).to.be(0); - }); - - it('should show the edit action for all panels', async () => { - await dashboard.switchToEditMode(); - - // All panels should be editable. This will catch cases where an error does not create an error embeddable. - const panelTitles = await dashboard.getPanelTitles(); - for (const title of panelTitles) { - await dashboardPanelActions.expectExistsEditPanelAction(title); - } - }); - - after(async () => { - await esArchiver.unload('x-pack/platform/test/fixtures/es_archives/logstash_functional'); - await kibanaServer.savedObjects.cleanStandardList(); - }); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts b/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts deleted file mode 100644 index bb0f9a4ec1119..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/migration_smoke_tests/visualize_migration_smoke_test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -/* This test is importing saved objects from 7.13.0 to 8.0 and the backported version - * will import from 6.8.x to 7.x.x - */ - -import expect from '@kbn/expect'; -import path from 'path'; -import type { FtrProviderContext } from '../../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardDrilldownPanelActions = getService('dashboardDrilldownPanelActions'); - - const { settings, savedObjects, dashboard } = getPageObjects([ - 'settings', - 'savedObjects', - 'dashboard', - ]); - - describe('Visualize - Export import saved objects between versions', () => { - before(async () => { - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/getting_started/shakespeare' - ); - await kibanaServer.uiSettings.replace({}); - await settings.navigateTo(); - await settings.clickKibanaSavedObjects(); - await savedObjects.importFile( - path.join(__dirname, 'exports', 'visualize_dashboard_migration_test_7_12_1.ndjson') - ); - }); - - after(async () => { - await esArchiver.unload( - 'x-pack/platform/test/fixtures/es_archives/getting_started/shakespeare' - ); - await kibanaServer.savedObjects.cleanStandardList(); - }); - - it('should be able to import dashboard with various Visualize panels from 7.12.1', async () => { - // this will catch cases where there is an error in the migrations. - await savedObjects.checkImportSucceeded(); - await savedObjects.clickImportDone(); - }); - - it('should render all panels on the dashboard', async () => { - await dashboard.navigateToApp(); - await dashboard.loadSavedDashboard('[7.12.1] Visualize Test Dashboard'); - - // dashboard should load properly - await dashboard.expectOnDashboard('[7.12.1] Visualize Test Dashboard'); - await dashboard.waitForRenderComplete(); - - // There should be 0 error embeddables on the dashboard - const errorEmbeddables = await testSubjects.findAll('embeddableStackError'); - expect(errorEmbeddables.length).to.be(0); - }); - - it('should show the edit action for all panels', async () => { - await dashboard.switchToEditMode(); - - // All panels should be editable. This will catch cases where an error does not create an error embeddable. - const panelTitles = await dashboard.getPanelTitles(); - for (const title of panelTitles) { - await dashboardPanelActions.expectExistsEditPanelAction(title); - } - }); - - it('should retain all panel drilldowns from 7.12.1', async () => { - // Both panels configured with drilldowns in 7.12.1 should still have drilldowns. - const totalPanels = await dashboard.getPanelCount(); - let panelsWithDrilldowns = 0; - for (let panelIndex = 0; panelIndex < totalPanels; panelIndex++) { - if ((await dashboardDrilldownPanelActions.getPanelDrilldownCount(panelIndex)) === 1) { - panelsWithDrilldowns++; - } - } - expect(panelsWithDrilldowns).to.be(2); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/panel_time_range.ts b/x-pack/platform/test/functional/apps/dashboard/group2/panel_time_range.ts deleted file mode 100644 index 02b656cefa4db..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/panel_time_range.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const testSubjects = getService('testSubjects'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardBadgeActions = getService('dashboardBadgeActions'); - const dashboardCustomizePanel = getService('dashboardCustomizePanel'); - const { dashboard, lens } = getPageObjects(['dashboard', 'lens']); - - const DASHBOARD_NAME = 'Custom panel time range test'; - - describe('custom time range', () => { - before(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/logstash_functional' - ); - await kibanaServer.importExport.load( - 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json' - ); - await dashboard.navigateToApp(); - await dashboard.preserveCrossAppState(); - await dashboard.clickNewDashboard(); - await dashboard.saveDashboard(DASHBOARD_NAME); - }); - - describe('by value', () => { - it('can add a custom time range to a panel', async () => { - await lens.createAndAddLensFromDashboard({}); - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.enableCustomTimeRange(); - await dashboardCustomizePanel.openDatePickerQuickMenu(); - await dashboardCustomizePanel.clickCommonlyUsedTimeRange('Last_30 days'); - await dashboardCustomizePanel.clickSaveButton(); - await dashboard.waitForRenderComplete(); - await dashboardBadgeActions.expectExistsTimeRangeBadgeAction(); - expect(await testSubjects.exists('emptyPlaceholder')).to.be(true); - await dashboard.clickQuickSave(); - }); - - it('can remove a custom time range from a panel', async () => { - await dashboardBadgeActions.clickTimeRangeBadgeAction(); - await dashboardCustomizePanel.disableCustomTimeRange(); - await dashboardCustomizePanel.clickSaveButton(); - await dashboard.waitForRenderComplete(); - await dashboardBadgeActions.expectMissingTimeRangeBadgeAction(); - expect(await testSubjects.exists('xyVisChart')).to.be(true); - }); - }); - - describe('by reference', () => { - it('can add a custom time range to panel', async () => { - await dashboardPanelActions.saveToLibrary('My by reference visualization'); - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.enableCustomTimeRange(); - await dashboardCustomizePanel.openDatePickerQuickMenu(); - await dashboardCustomizePanel.clickCommonlyUsedTimeRange('Last_30 days'); - await dashboardCustomizePanel.clickSaveButton(); - await dashboard.waitForRenderComplete(); - await dashboardBadgeActions.expectExistsTimeRangeBadgeAction(); - expect(await testSubjects.exists('emptyPlaceholder')).to.be(true); - await dashboard.clickQuickSave(); - }); - - it('can remove a custom time range from a panel', async () => { - await dashboardBadgeActions.clickTimeRangeBadgeAction(); - await dashboardCustomizePanel.disableCustomTimeRange(); - await dashboardCustomizePanel.clickSaveButton(); - await dashboard.waitForRenderComplete(); - await dashboardBadgeActions.expectMissingTimeRangeBadgeAction(); - expect(await testSubjects.exists('xyVisChart')).to.be(true); - }); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/panel_titles.ts b/x-pack/platform/test/functional/apps/dashboard/group2/panel_titles.ts deleted file mode 100644 index fb84f0dd93432..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/panel_titles.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import expect from '@kbn/expect'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const kibanaServer = getService('kibanaServer'); - const retry = getService('retry'); - const dashboardPanelActions = getService('dashboardPanelActions'); - const dashboardCustomizePanel = getService('dashboardCustomizePanel'); - const testSubjects = getService('testSubjects'); - const { dashboard, lens } = getPageObjects(['dashboard', 'lens']); - - const EMPTY_TITLE = undefined; - - describe('panel titles', () => { - before(async () => { - await kibanaServer.savedObjects.cleanStandardList(); - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/logstash_functional' - ); - await kibanaServer.importExport.load( - 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json' - ); - await dashboard.navigateToApp(); - await dashboard.preserveCrossAppState(); - await dashboard.clickNewDashboard(); - await dashboard.saveDashboard('Panel Title Test'); - await lens.createAndAddLensFromDashboard({}); - }); - - beforeEach(async () => { - // close any open flyouts to prevent dirty state between tests - if (await testSubjects.exists('euiFlyoutCloseButton')) { - await testSubjects.click('euiFlyoutCloseButton'); - } - }); - - describe('by value', () => { - it('new panel by value has empty title', async () => { - const [newPanelTitle] = await dashboard.getPanelTitles(); - expect(newPanelTitle).to.equal(EMPTY_TITLE); - }); - - it('saving new panel with blank title clears "unsaved changes" badge', async () => { - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelTitle(''); - await dashboardCustomizePanel.clickSaveButton(); - await dashboard.clearUnsavedChanges(); - }); - - it('custom title causes unsaved changes and saving clears it', async () => { - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelTitle('Custom title'); - await dashboardCustomizePanel.clickSaveButton(); - const [panelTitle] = await dashboard.getPanelTitles(); - expect(panelTitle).to.equal('Custom title'); - await dashboard.clearUnsavedChanges(); - }); - - it('reset title should be hidden on a by value panel', async () => { - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelTitle('Some title'); - await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.customizePanel(); - expect(await testSubjects.exists('resetCustomEmbeddablePanelTitleButton')).to.be(false); - }); - - it('reset description should be hidden on a by value panel', async () => { - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelDescription('Some description'); - await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.customizePanel(); - expect(await testSubjects.exists('resetCustomEmbeddablePanelDescriptionButton')).to.be( - false - ); - }); - }); - - describe('by reference', () => { - const VIS_LIBRARY_DESCRIPTION = 'Vis library description'; - - let count = 0; - const getVisTitle = (increment = false) => - `Vis Library Title - ${increment ? ++count : count}`; - - it('linking a by value panel with a custom title to the library will overwrite the custom title with the library title', async () => { - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelTitle('Custom title'); - await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.saveToLibrary(getVisTitle(true)); - await retry.tryForTime(500, async () => { - // need to surround in 'retry' due to delays in HTML updates causing the title read to be behind - const [newPanelTitle] = await dashboard.getPanelTitles(); - expect(newPanelTitle).to.equal(getVisTitle()); - }); - }); - - it('resetting title on a by reference panel sets it to the library title', async () => { - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelTitle('Custom Title'); - await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.resetCustomPanelTitle(); - await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.customizePanel(); - expect(await dashboardCustomizePanel.getCustomPanelTitle()).to.equal(getVisTitle()); - }); - - it('resetting description on a by reference panel sets it to the library title', async () => { - await dashboardPanelActions.navigateToEditorFromFlyout(); - await lens.save( - getVisTitle(true), - false, - undefined, - undefined, - undefined, - undefined, - VIS_LIBRARY_DESCRIPTION - ); - - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelDescription('Custom description'); - await dashboardCustomizePanel.clickSaveButton(); - - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.resetCustomPanelDescription(); - await dashboardCustomizePanel.clickSaveButton(); - - await dashboardPanelActions.customizePanel(); - expect(await dashboardCustomizePanel.getCustomPanelDescription()).to.equal( - VIS_LIBRARY_DESCRIPTION - ); - }); - - it('unlinking a by reference panel with a custom title will keep the current title', async () => { - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelTitle('Custom title'); - await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.unlinkFromLibrary('Custom title'); - const [newPanelTitle] = await dashboard.getPanelTitles(); - expect(newPanelTitle).to.equal('Custom title'); - }); - - it("linking a by value panel with a blank title to the library will set the panel's title to the library title", async () => { - await dashboardPanelActions.customizePanel(); - await dashboardCustomizePanel.setCustomPanelTitle(''); - await dashboardCustomizePanel.clickSaveButton(); - await dashboardPanelActions.saveToLibrary(getVisTitle(true)); - await retry.tryForTime(500, async () => { - // need to surround in 'retry' due to delays in HTML updates causing the title read to be behind - const [newPanelTitle] = await dashboard.getPanelTitles(); - expect(newPanelTitle).to.equal(getVisTitle()); - }); - }); - - it('unlinking a by reference panel without a custom title will keep the library title', async () => { - await dashboardPanelActions.unlinkFromLibrary(getVisTitle()); - const [newPanelTitle] = await dashboard.getPanelTitles(); - expect(newPanelTitle).to.equal(getVisTitle()); - }); - }); - }); -} diff --git a/x-pack/platform/test/functional/apps/dashboard/group2/sync_colors.ts b/x-pack/platform/test/functional/apps/dashboard/group2/sync_colors.ts deleted file mode 100644 index 88a12bdd8fb2d..0000000000000 --- a/x-pack/platform/test/functional/apps/dashboard/group2/sync_colors.ts +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { DebugState } from '@elastic/charts'; -import expect from '@kbn/expect'; -import chroma from 'chroma-js'; -import type { FtrProviderContext } from '../../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const { dashboard, header, lens } = getPageObjects(['dashboard', 'header', 'lens']); - const dashboardAddPanel = getService('dashboardAddPanel'); - const dashboardSettings = getService('dashboardSettings'); - const filterBar = getService('filterBar'); - const elasticChart = getService('elasticChart'); - const kibanaServer = getService('kibanaServer'); - - function getColorMapping(debugState: DebugState | null) { - if (!debugState) return {}; - const colorMapping: Record = {}; - debugState.bars?.forEach(({ name, color }) => { - colorMapping[name] = color; - }); - - return colorMapping; - } - - // Failing: See https://github.com/elastic/kibana/issues/235883 - describe.skip('sync colors', function () { - before(async function () { - await esArchiver.loadIfNeeded( - 'x-pack/platform/test/fixtures/es_archives/logstash_functional' - ); - await kibanaServer.importExport.load( - 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json' - ); - }); - - after(async function () { - await esArchiver.unload('x-pack/platform/test/fixtures/es_archives/logstash_functional'); - await kibanaServer.importExport.unload( - 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json' - ); - await kibanaServer.savedObjects.cleanStandardList(); - }); - - it('should sync colors on dashboard for legacy default palette', async function () { - await dashboard.navigateToApp(); - await elasticChart.setNewChartUiDebugFlag(true); - await dashboard.clickCreateDashboardPrompt(); - - // create non-filtered xy chart - await dashboardAddPanel.clickCreateNewLink(); - await lens.goToTimeRange(); - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'count', - field: 'Records', - }); - await lens.configureDimension({ - dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', - operation: 'terms', - field: 'geo.src', - palette: { mode: 'legacy', id: 'default' }, - }); - await lens.saveAndReturn(); - await header.waitUntilLoadingHasFinished(); - - // create filtered xy chart - await dashboardAddPanel.clickCreateNewLink(); - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'count', - field: 'Records', - }); - await lens.configureDimension({ - dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', - operation: 'terms', - field: 'geo.src', - palette: { mode: 'legacy', id: 'default' }, - }); - await filterBar.addFilter({ field: 'geo.src', operation: 'is not', value: 'CN' }); - await lens.saveAndReturn(); - await header.waitUntilLoadingHasFinished(); - - // create datatable vis - await dashboardAddPanel.clickCreateNewLink(); - await lens.switchToVisualization('lnsDatatable'); - await lens.configureDimension({ - dimension: 'lnsDatatable_rows > lns-empty-dimension', - operation: 'terms', - field: 'geo.src', - keepOpen: true, - }); - await lens.setTermsNumberOfValues(5); - await lens.setTableDynamicColoring('cell'); - await lens.setPalette('default', true); - await lens.closeDimensionEditor(); - await lens.configureDimension({ - dimension: 'lnsDatatable_metrics > lns-empty-dimension', - operation: 'count', - field: 'Records', - }); - await lens.saveAndReturn(); - - // Set dashboard to sync colors - await dashboard.openSettingsFlyout(); - await dashboardSettings.toggleSyncColors(true); - await dashboardSettings.clickApplyButton(); - await header.waitUntilLoadingHasFinished(); - await dashboard.waitForRenderComplete(); - - const colorMappings1 = Object.entries( - getColorMapping(await dashboard.getPanelChartDebugState(0)) - ); - const colorMappings2 = Object.entries( - getColorMapping(await dashboard.getPanelChartDebugState(1)) - ); - - const els = await lens.getDatatableCellsByColumn(0); - const colorMappings3 = await Promise.all( - els.map(async (el) => [ - await el.getVisibleText(), - chroma((await lens.getStylesFromCell(el))['background-color']).hex(), // eui converts hex to rgb - ]) - ); - - expect(colorMappings1).to.have.length(6); - expect(colorMappings2).to.have.length(6); - expect(colorMappings3).to.have.length(6); - - const mergedColorAssignments = new Map>(); - - [...colorMappings1, ...colorMappings2, ...colorMappings3].forEach(([key, color]) => { - if (!mergedColorAssignments.has(key)) mergedColorAssignments.set(key, new Set()); - mergedColorAssignments.get(key)?.add(color); - }); - - // Each key should have only been assigned one color across all 3 visualizations - mergedColorAssignments.forEach((colors, key) => { - expect(colors.size).eql( - 1, - `Key "${key}" was assigned multiple colors: ${JSON.stringify([...colors])}` - ); - }); - }); - - it('should be possible to disable color sync', async () => { - await dashboard.openSettingsFlyout(); - await dashboardSettings.toggleSyncColors(false); - await dashboardSettings.clickApplyButton(); - await header.waitUntilLoadingHasFinished(); - const colorMapping1 = getColorMapping(await dashboard.getPanelChartDebugState(0)); - const colorMapping2 = getColorMapping(await dashboard.getPanelChartDebugState(1)); - const colorsByOrder1 = Object.values(colorMapping1); - const colorsByOrder2 = Object.values(colorMapping2); - // colors by order of occurence have to be the same - expect(colorsByOrder1).to.eql(colorsByOrder2); - }); - }); -}