diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 8924d22cdb50f..cc1420e4825c2 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -33,6 +33,7 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide const dashboardAddPanel = getService('dashboardAddPanel'); const renderable = getService('renderable'); const listingTable = getService('listingTable'); + const elasticChart = getService('elasticChart'); const PageObjects = getPageObjects(['common', 'header', 'visualize']); interface SaveDashboardOptions { @@ -275,6 +276,20 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide } } + public async isColorSyncOn() { + log.debug('isColorSyncOn'); + await this.openOptions(); + return await testSubjects.getAttribute('dashboardSyncColorsCheckbox', 'checked'); + } + + public async useColorSync(on = true) { + await this.openOptions(); + const isColorSyncOn = await this.isColorSyncOn(); + if (isColorSyncOn !== 'on') { + return await testSubjects.click('dashboardSyncColorsCheckbox'); + } + } + public async gotoDashboardEditMode(dashboardName: string) { await this.loadSavedDashboard(dashboardName); await this.switchToEditMode(); @@ -554,6 +569,10 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide return 0; } } + + public async getPanelChartDebugState(panelIndex: number) { + return await elasticChart.getChartDebugData(undefined, panelIndex); + } } return new DashboardPage(); diff --git a/test/functional/services/visualizations/elastic_chart.ts b/test/functional/services/visualizations/elastic_chart.ts index 1f1f7df45f460..86ca4d1c1e31e 100644 --- a/test/functional/services/visualizations/elastic_chart.ts +++ b/test/functional/services/visualizations/elastic_chart.ts @@ -81,19 +81,23 @@ export function ElasticChartProvider({ getService }: FtrProviderContext) { } } - private async getChart(dataTestSubj?: string, timeout?: number): Promise { + private async getChart( + dataTestSubj?: string, + timeout?: number, + match: number = 0 + ): Promise { if (dataTestSubj) { if (!(await testSubjects.exists(dataTestSubj, { timeout }))) { throw Error(`Failed to find an elastic-chart with testSubject '${dataTestSubj}'`); } - return await testSubjects.find(dataTestSubj); + return (await testSubjects.findAll(dataTestSubj))[match]; } else { const charts = await this.getAllCharts(timeout); if (charts.length === 0) { throw Error(`Failed to find any elastic-charts on the page`); } else { - return charts[0]; + return charts[match]; } } } @@ -106,8 +110,11 @@ export function ElasticChartProvider({ getService }: FtrProviderContext) { * used to get chart data from `@elastic/charts` * requires `window._echDebugStateFlag` to be true */ - public async getChartDebugData(dataTestSubj?: string): Promise { - const chart = await this.getChart(dataTestSubj); + public async getChartDebugData( + dataTestSubj?: string, + match: number = 0 + ): Promise { + const chart = await this.getChart(dataTestSubj, undefined, match); try { const visContainer = await chart.findByCssSelector('.echChartStatus'); diff --git a/x-pack/test/functional/apps/dashboard/index.ts b/x-pack/test/functional/apps/dashboard/index.ts index 4a893d3f62d93..1ba87f89762a1 100644 --- a/x-pack/test/functional/apps/dashboard/index.ts +++ b/x-pack/test/functional/apps/dashboard/index.ts @@ -13,6 +13,7 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./preserve_url')); loadTestFile(require.resolve('./reporting')); loadTestFile(require.resolve('./drilldowns')); + loadTestFile(require.resolve('./sync_colors')); loadTestFile(require.resolve('./_async_dashboard')); }); } diff --git a/x-pack/test/functional/apps/dashboard/sync_colors.ts b/x-pack/test/functional/apps/dashboard/sync_colors.ts new file mode 100644 index 0000000000000..99fe953a84cd4 --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/sync_colors.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DebugState } from '@elastic/charts'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects([ + 'common', + 'dashboard', + 'spaceSelector', + 'header', + 'lens', + 'timePicker', + ]); + const dashboardAddPanel = getService('dashboardAddPanel'); + const filterBar = getService('filterBar'); + const elasticChart = getService('elasticChart'); + + function getColorMapping(debugState: DebugState | null) { + if (!debugState) return {}; + const colorMapping: Record = {}; + debugState.bars?.forEach(({ name, color }) => { + colorMapping[name] = color; + }); + + return colorMapping; + } + + describe('sync colors', function () { + before(async function () { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('lens/basic'); + }); + + after(async function () { + await esArchiver.unload('logstash_functional'); + await esArchiver.unload('lens/basic'); + }); + + it('should sync colors on dashboard by default', async function () { + await PageObjects.common.navigateToApp('dashboard'); + await elasticChart.setNewChartUiDebugFlag(true); + await PageObjects.dashboard.clickCreateDashboardPrompt(); + await dashboardAddPanel.clickCreateNewLink(); + await dashboardAddPanel.clickVisType('lens'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'count', + field: 'Records', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + }); + + await PageObjects.lens.save('vis1', true, true); + await PageObjects.header.waitUntilLoadingHasFinished(); + await dashboardAddPanel.clickCreateNewLink(); + await dashboardAddPanel.clickVisType('lens'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'count', + field: 'Records', + }); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + }); + + await filterBar.addFilter('geo.src', 'is not', 'CN'); + + await PageObjects.lens.save('vis2', true, true); + await PageObjects.header.waitUntilLoadingHasFinished(); + const colorMapping1 = getColorMapping(await PageObjects.dashboard.getPanelChartDebugState(0)); + const colorMapping2 = getColorMapping(await PageObjects.dashboard.getPanelChartDebugState(1)); + expect(Object.keys(colorMapping1)).to.have.length(6); + expect(Object.keys(colorMapping1)).to.have.length(6); + const panel1Keys = ['CN']; + const panel2Keys = ['PK']; + const sharedKeys = ['IN', 'US', 'ID', 'BR', 'Other']; + // colors for keys exclusive to panel 1 should not occur in panel 2 + panel1Keys.forEach((panel1Key) => { + const assignedColor = colorMapping1[panel1Key]; + expect(Object.values(colorMapping2)).not.to.contain(assignedColor); + }); + // colors for keys exclusive to panel 2 should not occur in panel 1 + panel2Keys.forEach((panel2Key) => { + const assignedColor = colorMapping2[panel2Key]; + expect(Object.values(colorMapping1)).not.to.contain(assignedColor); + }); + // colors for keys used in both panels should be synced + sharedKeys.forEach((sharedKey) => { + expect(colorMapping1[sharedKey]).to.eql(colorMapping2[sharedKey]); + }); + }); + + it('should be possible to disable color sync', async () => { + await PageObjects.dashboard.useColorSync(false); + await PageObjects.header.waitUntilLoadingHasFinished(); + const colorMapping1 = getColorMapping(await PageObjects.dashboard.getPanelChartDebugState(0)); + const colorMapping2 = getColorMapping(await PageObjects.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); + }); + }); +}