diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx
index 5e2fe9d7bbc14b..a62db353e2bafa 100644
--- a/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx
+++ b/x-pack/plugins/lens/public/indexpattern_datasource/change_indexpattern.tsx
@@ -57,7 +57,7 @@ export function ChangeIndexPattern({
panelPaddingSize="s"
ownFocus
>
-
+
{i18n.translate('xpack.lens.indexPattern.changeIndexPatternTitle', {
defaultMessage: 'Change index pattern',
diff --git a/x-pack/plugins/lens/public/pie_visualization/pie_visualization.tsx b/x-pack/plugins/lens/public/pie_visualization/pie_visualization.tsx
index 369ab28293fbc8..5a68516db6aa3f 100644
--- a/x-pack/plugins/lens/public/pie_visualization/pie_visualization.tsx
+++ b/x-pack/plugins/lens/public/pie_visualization/pie_visualization.tsx
@@ -122,6 +122,7 @@ export const pieVisualization: Visualization {
);
const options = component
- .find('[data-test-subj="lnsXY_seriesType"]')
+ .find(EuiButtonGroup)
.first()
.prop('options') as EuiButtonGroupProps['options'];
@@ -79,7 +79,7 @@ describe('XY Config panels', () => {
);
const options = component
- .find('[data-test-subj="lnsXY_seriesType"]')
+ .find(EuiButtonGroup)
.first()
.prop('options') as EuiButtonGroupProps['options'];
diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
index 6d5bc7808a678f..e4bc6de5cc68ae 100644
--- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
+++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx
@@ -95,13 +95,13 @@ export function LayerContextMenu(props: VisualizationLayerWidgetProps) {
})}
name="chartType"
className="eui-displayInlineBlock"
- data-test-subj="lnsXY_seriesType"
options={visualizationTypes
.filter((t) => isHorizontalSeries(t.id as SeriesType) === horizontalOnly)
.map((t) => ({
id: t.id,
label: t.label,
iconType: t.icon || 'empty',
+ 'data-test-subj': `lnsXY_seriesType-${t.id}`,
}))}
idSelected={layer.seriesType}
onChange={(seriesType) => {
diff --git a/x-pack/test/api_integration/apis/lens/telemetry.ts b/x-pack/test/api_integration/apis/lens/telemetry.ts
index 2c05b02cf470f5..0ae4753cd2967d 100644
--- a/x-pack/test/api_integration/apis/lens/telemetry.ts
+++ b/x-pack/test/api_integration/apis/lens/telemetry.ts
@@ -191,8 +191,9 @@ export default ({ getService }: FtrProviderContext) => {
expect(results.saved_overall).to.eql({
lnsMetric: 1,
bar_stacked: 1,
+ lnsPie: 1,
});
- expect(results.saved_overall_total).to.eql(2);
+ expect(results.saved_overall_total).to.eql(3);
await esArchiver.unload('lens/basic');
});
diff --git a/x-pack/test/functional/apps/lens/dashboard.ts b/x-pack/test/functional/apps/lens/dashboard.ts
new file mode 100644
index 00000000000000..ccf2f88a9d0ed1
--- /dev/null
+++ b/x-pack/test/functional/apps/lens/dashboard.ts
@@ -0,0 +1,64 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { FtrProviderContext } from '../../ftr_provider_context';
+
+export default function ({ getService, getPageObjects }: FtrProviderContext) {
+ const PageObjects = getPageObjects(['header', 'common', 'dashboard', 'timePicker', 'lens']);
+
+ const find = getService('find');
+ const dashboardAddPanel = getService('dashboardAddPanel');
+ const elasticChart = getService('elasticChart');
+ const browser = getService('browser');
+ const retry = getService('retry');
+ const testSubjects = getService('testSubjects');
+ const filterBar = getService('filterBar');
+
+ async function clickInChart(x: number, y: number) {
+ const el = await elasticChart.getCanvas();
+ await browser.getActions().move({ x, y, origin: el._webElement }).click().perform();
+ }
+
+ describe('lens dashboard tests', () => {
+ it('metric should be embeddable', async () => {
+ await PageObjects.common.navigateToApp('dashboard');
+ await PageObjects.dashboard.clickNewDashboard();
+ await dashboardAddPanel.clickOpenAddPanel();
+ await dashboardAddPanel.filterEmbeddableNames('Artistpreviouslyknownaslens');
+ await find.clickByButtonText('Artistpreviouslyknownaslens');
+ await dashboardAddPanel.closeAddPanel();
+ await PageObjects.lens.goToTimeRange();
+ await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
+ });
+
+ it('should be able to add filters/timerange by clicking in XYChart', async () => {
+ await PageObjects.common.navigateToApp('dashboard');
+ await PageObjects.dashboard.clickNewDashboard();
+ await dashboardAddPanel.clickOpenAddPanel();
+ await dashboardAddPanel.filterEmbeddableNames('lnsXYvis');
+ await find.clickByButtonText('lnsXYvis');
+ await dashboardAddPanel.closeAddPanel();
+ await PageObjects.lens.goToTimeRange();
+ await clickInChart(5, 5); // hardcoded position of bar
+
+ await retry.try(async () => {
+ await testSubjects.click('applyFiltersPopoverButton');
+ await testSubjects.missingOrFail('applyFiltersPopoverButton');
+ });
+
+ await PageObjects.lens.assertExactText(
+ '[data-test-subj="embeddablePanelHeading-lnsXYvis"]',
+ 'lnsXYvis'
+ );
+ const time = await PageObjects.timePicker.getTimeConfig();
+ expect(time.start).to.equal('Sep 21, 2015 @ 09:00:00.000');
+ expect(time.end).to.equal('Sep 21, 2015 @ 12:00:00.000');
+ const hasIpFilter = await filterBar.hasFilter('ip', '97.220.3.248');
+ expect(hasIpFilter).to.be(true);
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/lens/index.ts b/x-pack/test/functional/apps/lens/index.ts
index b17b7d856841c9..f2dcf28c017433 100644
--- a/x-pack/test/functional/apps/lens/index.ts
+++ b/x-pack/test/functional/apps/lens/index.ts
@@ -28,6 +28,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) {
this.tags(['ciGroup4', 'skipFirefox']);
loadTestFile(require.resolve('./smokescreen'));
+ loadTestFile(require.resolve('./dashboard'));
loadTestFile(require.resolve('./persistent_context'));
loadTestFile(require.resolve('./lens_reporting'));
});
diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts
index 1e936361610678..77b9aa1e25edd8 100644
--- a/x-pack/test/functional/apps/lens/smokescreen.ts
+++ b/x-pack/test/functional/apps/lens/smokescreen.ts
@@ -8,115 +8,20 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService, getPageObjects }: FtrProviderContext) {
- const PageObjects = getPageObjects([
- 'header',
- 'common',
- 'visualize',
- 'dashboard',
- 'header',
- 'timePicker',
- 'lens',
- ]);
+ const PageObjects = getPageObjects(['visualize', 'lens']);
const find = getService('find');
- const dashboardAddPanel = getService('dashboardAddPanel');
- const elasticChart = getService('elasticChart');
- const browser = getService('browser');
- const retry = getService('retry');
- const testSubjects = getService('testSubjects');
- const filterBar = getService('filterBar');
const listingTable = getService('listingTable');
- async function assertExpectedMetric(metricCount: string = '19,986') {
- await PageObjects.lens.assertExactText(
- '[data-test-subj="lns_metric_title"]',
- 'Maximum of bytes'
- );
- await PageObjects.lens.assertExactText('[data-test-subj="lns_metric_value"]', metricCount);
- }
-
- async function assertExpectedTable() {
- await PageObjects.lens.assertExactText(
- '[data-test-subj="lnsDataTable"] thead .euiTableCellContent__text',
- 'Maximum of bytes'
- );
- await PageObjects.lens.assertExactText(
- '[data-test-subj="lnsDataTable"] [data-test-subj="lnsDataTableCellValue"]',
- '19,986'
- );
- }
-
- async function assertExpectedChart() {
- await PageObjects.lens.assertExactText(
- '[data-test-subj="embeddablePanelHeading-lnsXYvis"]',
- 'lnsXYvis'
- );
- }
-
- async function assertExpectedTimerange() {
- const time = await PageObjects.timePicker.getTimeConfig();
- expect(time.start).to.equal('Sep 21, 2015 @ 09:00:00.000');
- expect(time.end).to.equal('Sep 21, 2015 @ 12:00:00.000');
- }
-
- async function clickOnBarHistogram() {
- const el = await elasticChart.getCanvas();
- await browser.getActions().move({ x: 5, y: 5, origin: el._webElement }).click().perform();
- }
-
describe('lens smokescreen tests', () => {
it('should allow editing saved visualizations', async () => {
await PageObjects.visualize.gotoVisualizationLandingPage();
await listingTable.searchForItemWithName('Artistpreviouslyknownaslens');
await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens');
await PageObjects.lens.goToTimeRange();
- await assertExpectedMetric();
- });
-
- it('metric should be embeddable in dashboards', async () => {
- await PageObjects.common.navigateToApp('dashboard');
- await PageObjects.dashboard.clickNewDashboard();
- await dashboardAddPanel.clickOpenAddPanel();
- await dashboardAddPanel.filterEmbeddableNames('Artistpreviouslyknownaslens');
- await find.clickByButtonText('Artistpreviouslyknownaslens');
- await dashboardAddPanel.closeAddPanel();
- await PageObjects.lens.goToTimeRange();
- await assertExpectedMetric();
+ await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
});
- it('click on the bar in XYChart adds proper filters/timerange in dashboard', async () => {
- await PageObjects.common.navigateToApp('dashboard');
- await PageObjects.dashboard.clickNewDashboard();
- await dashboardAddPanel.clickOpenAddPanel();
- await dashboardAddPanel.filterEmbeddableNames('lnsXYvis');
- await find.clickByButtonText('lnsXYvis');
- await dashboardAddPanel.closeAddPanel();
- await PageObjects.lens.goToTimeRange();
- await clickOnBarHistogram();
-
- await retry.try(async () => {
- await testSubjects.click('applyFiltersPopoverButton');
- await testSubjects.missingOrFail('applyFiltersPopoverButton');
- });
-
- await assertExpectedChart();
- await assertExpectedTimerange();
- const hasIpFilter = await filterBar.hasFilter('ip', '97.220.3.248');
- expect(hasIpFilter).to.be(true);
- });
-
- it('should allow seamless transition to and from table view', async () => {
- await PageObjects.visualize.gotoVisualizationLandingPage();
- await listingTable.searchForItemWithName('Artistpreviouslyknownaslens');
- await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens');
- await PageObjects.lens.goToTimeRange();
- await assertExpectedMetric();
- await PageObjects.lens.switchToVisualization('lnsDatatable');
- await assertExpectedTable();
- await PageObjects.lens.switchToVisualization('lnsMetric');
- await assertExpectedMetric();
- });
-
- it('should allow creation of lens visualizations', async () => {
+ it('should allow creation of lens xy chart', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
await PageObjects.lens.goToTimeRange();
@@ -165,6 +70,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await find.allByCssSelector('.echLegendItem')).to.have.length(3);
});
+ it('should allow seamless transition to and from table view', async () => {
+ await PageObjects.visualize.gotoVisualizationLandingPage();
+ await listingTable.searchForItemWithName('Artistpreviouslyknownaslens');
+ await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens');
+ await PageObjects.lens.goToTimeRange();
+ await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
+ await PageObjects.lens.switchToVisualization('lnsDatatable');
+ expect(await PageObjects.lens.getDatatableHeaderText()).to.eql('Maximum of bytes');
+ expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('19,986');
+ await PageObjects.lens.switchToVisualization('lnsMetric');
+ await PageObjects.lens.assertMetric('Maximum of bytes', '19,986');
+ });
+
it('should switch from a multi-layer stacked bar to a multi-layer line chart', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
@@ -190,5 +108,94 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await PageObjects.lens.getLayerCount()).to.eql(2);
});
+
+ it('should allow transition from line chart to donut chart and to bar chart', async () => {
+ await PageObjects.visualize.gotoVisualizationLandingPage();
+ await listingTable.searchForItemWithName('lnsXYvis');
+ await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis');
+ await PageObjects.lens.goToTimeRange();
+ expect(await PageObjects.lens.hasChartSwitchWarning('donut')).to.eql(true);
+ await PageObjects.lens.switchToVisualization('donut');
+
+ expect(await PageObjects.lens.getTitle()).to.eql('lnsXYvis');
+ expect(await PageObjects.lens.getDimensionTriggerText('lnsPie_sliceByDimensionPanel')).to.eql(
+ 'Top values of ip'
+ );
+ expect(await PageObjects.lens.getDimensionTriggerText('lnsPie_sizeByDimensionPanel')).to.eql(
+ 'Average of bytes'
+ );
+
+ expect(await PageObjects.lens.hasChartSwitchWarning('bar')).to.eql(false);
+ await PageObjects.lens.switchToVisualization('bar');
+ expect(await PageObjects.lens.getTitle()).to.eql('lnsXYvis');
+ expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel')).to.eql(
+ 'Top values of ip'
+ );
+ expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql(
+ 'Average of bytes'
+ );
+ });
+
+ it('should allow seamless transition from bar chart to line chart using layer chart switch', async () => {
+ await PageObjects.visualize.gotoVisualizationLandingPage();
+ await listingTable.searchForItemWithName('lnsXYvis');
+ await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis');
+ await PageObjects.lens.goToTimeRange();
+ await PageObjects.lens.switchLayerSeriesType('line');
+ expect(await PageObjects.lens.getTitle()).to.eql('lnsXYvis');
+ expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_xDimensionPanel')).to.eql(
+ '@timestamp'
+ );
+ expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql(
+ 'Average of bytes'
+ );
+ expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_splitDimensionPanel')).to.eql(
+ 'Top values of ip'
+ );
+ });
+
+ it('should allow seamless transition from pie chart to treemap chart', async () => {
+ await PageObjects.visualize.gotoVisualizationLandingPage();
+ await listingTable.searchForItemWithName('lnsPieVis');
+ await PageObjects.lens.clickVisualizeListItemTitle('lnsPieVis');
+ await PageObjects.lens.goToTimeRange();
+ expect(await PageObjects.lens.hasChartSwitchWarning('treemap')).to.eql(false);
+ await PageObjects.lens.switchToVisualization('treemap');
+ expect(
+ await PageObjects.lens.getDimensionTriggerText('lnsPie_groupByDimensionPanel', 0)
+ ).to.eql('Top values of geo.dest');
+ expect(
+ await PageObjects.lens.getDimensionTriggerText('lnsPie_groupByDimensionPanel', 1)
+ ).to.eql('Top values of geo.src');
+ expect(await PageObjects.lens.getDimensionTriggerText('lnsPie_sizeByDimensionPanel')).to.eql(
+ 'Average of bytes'
+ );
+ });
+
+ it('should allow creating a pie chart and switching to datatable', async () => {
+ await PageObjects.visualize.navigateToNewVisualization();
+ await PageObjects.visualize.clickVisType('lens');
+ await PageObjects.lens.goToTimeRange();
+ await PageObjects.lens.switchToVisualization('pie');
+ await PageObjects.lens.configureDimension({
+ dimension: 'lnsPie_sliceByDimensionPanel > lns-empty-dimension',
+ operation: 'date_histogram',
+ field: '@timestamp',
+ });
+
+ await PageObjects.lens.configureDimension({
+ dimension: 'lnsPie_sizeByDimensionPanel > lns-empty-dimension',
+ operation: 'avg',
+ field: 'bytes',
+ });
+
+ expect(await PageObjects.lens.hasChartSwitchWarning('lnsDatatable')).to.eql(false);
+ await PageObjects.lens.switchToVisualization('lnsDatatable');
+
+ expect(await PageObjects.lens.getDatatableHeaderText()).to.eql('@timestamp per 3 hours');
+ expect(await PageObjects.lens.getDatatableCellText(0, 0)).to.eql('2015-09-20 00:00');
+ expect(await PageObjects.lens.getDatatableHeaderText(1)).to.eql('Average of bytes');
+ expect(await PageObjects.lens.getDatatableCellText(0, 1)).to.eql('6,011.351');
+ });
});
}
diff --git a/x-pack/test/functional/es_archives/lens/basic/data.json.gz b/x-pack/test/functional/es_archives/lens/basic/data.json.gz
index 4ed7c29f7391e4..ddf4a27289dffd 100644
Binary files a/x-pack/test/functional/es_archives/lens/basic/data.json.gz and b/x-pack/test/functional/es_archives/lens/basic/data.json.gz differ
diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts
index 79548db0e26301..bed0e3a159e23f 100644
--- a/x-pack/test/functional/page_objects/lens_page.ts
+++ b/x-pack/test/functional/page_objects/lens_page.ts
@@ -176,9 +176,26 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
*/
async hasChartSwitchWarning(subVisualizationId: string) {
await this.openChartSwitchPopover();
-
const element = await testSubjects.find(`lnsChartSwitchPopover_${subVisualizationId}`);
- return await testSubjects.descendantExists('euiKeyPadMenuItem__betaBadgeWrapper', element);
+ return await find.descendantExistsByCssSelector(
+ '.euiKeyPadMenuItem__betaBadgeWrapper',
+ element
+ );
+ },
+
+ /**
+ * Uses the Lens layer switcher to switch seriesType for xy charts.
+ *
+ * @param subVisualizationId - the ID of the sub-visualization to switch to, such as
+ * line,
+ */
+ async switchLayerSeriesType(seriesType: string) {
+ await retry.try(async () => {
+ await testSubjects.click('lns_layer_settings');
+ await testSubjects.exists(`lnsXY_seriesType-${seriesType}`);
+ });
+
+ return await testSubjects.click(`lnsXY_seriesType-${seriesType}`);
},
/**
@@ -205,5 +222,60 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
await PageObjects.header.waitUntilLoadingHasFinished();
await testSubjects.missingOrFail('lnsApp_saveAndReturnButton');
},
+ /**
+ * Gets label of dimension trigger in dimension panel
+ *
+ * @param dimension - the selector of the dimension
+ */
+ async getDimensionTriggerText(dimension: string, index = 0) {
+ const dimensionElements = await testSubjects.findAll(dimension);
+ const trigger = await testSubjects.findDescendant(
+ 'lns-dimensionTrigger',
+ dimensionElements[index]
+ );
+ return await trigger.getVisibleText();
+ },
+
+ /**
+ * Gets text of the specified datatable header cell
+ *
+ * @param index - index of th element in datatable
+ */
+ async getDatatableHeaderText(index = 0) {
+ return find
+ .byCssSelector(
+ `[data-test-subj="lnsDataTable"] thead th:nth-child(${
+ index + 1
+ }) .euiTableCellContent__text`
+ )
+ .then((el) => el.getVisibleText());
+ },
+
+ /**
+ * Gets text of the specified datatable cell
+ *
+ * @param rowIndex - index of row of the cell
+ * @param colIndex - index of column of the cell
+ */
+ async getDatatableCellText(rowIndex = 0, colIndex = 0) {
+ return find
+ .byCssSelector(
+ `[data-test-subj="lnsDataTable"] tr:nth-child(${rowIndex + 1}) td:nth-child(${
+ colIndex + 1
+ })`
+ )
+ .then((el) => el.getVisibleText());
+ },
+
+ /**
+ * Asserts that metric has expected title and count
+ *
+ * @param title - expected title
+ * @param count - expected count of metric
+ */
+ async assertMetric(title: string, count: string) {
+ await this.assertExactText('[data-test-subj="lns_metric_title"]', title);
+ await this.assertExactText('[data-test-subj="lns_metric_value"]', count);
+ },
});
}