diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx index 91adbcecaf89..077e07a89f78 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/filters/filter_popover.tsx @@ -118,6 +118,7 @@ export const QueryInput = ({ return ( void }) => ( - + {label} ); diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index 365e430a460f..50b8f4c6fc40 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -126,6 +126,7 @@ export function PieToolbar(props: VisualizationToolbarProps & { * Adjusts the borders for groupings */ groupPosition?: 'none' | 'left' | 'center' | 'right'; + dataTestSubj?: string; }; export const ToolbarButton: React.FunctionComponent = ({ @@ -42,6 +43,7 @@ export const ToolbarButton: React.FunctionComponent = ({ size = 'm', hasArrow = true, groupPosition = 'none', + dataTestSubj = '', ...rest }) => { const classes = classNames( @@ -52,6 +54,7 @@ export const ToolbarButton: React.FunctionComponent = ({ ); return ( = ({ @@ -39,6 +40,7 @@ export const ToolbarPopover: React.FunctionComponent = ({ type, isDisabled = false, groupPosition, + buttonDataTestSubj, }) => { const [open, setOpen] = useState(false); @@ -60,6 +62,7 @@ export const ToolbarPopover: React.FunctionComponent = ({ hasArrow={false} isDisabled={isDisabled} groupPosition={groupPosition} + dataTestSubj={buttonDataTestSubj} > diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx index 835f3e2cde76..45ec7098aa63 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx @@ -73,7 +73,12 @@ export interface AxisSettingsPopoverProps { const popoverConfig = ( axis: AxesSettingsConfigKeys, isHorizontal: boolean -): { icon: IconType; groupPosition: ToolbarButtonProps['groupPosition']; popoverTitle: string } => { +): { + icon: IconType; + groupPosition: ToolbarButtonProps['groupPosition']; + popoverTitle: string; + buttonDataTestSubj: string; +} => { switch (axis) { case 'yLeft': return { @@ -86,6 +91,7 @@ const popoverConfig = ( : i18n.translate('xpack.lens.xyChart.leftAxisLabel', { defaultMessage: 'Left axis', }), + buttonDataTestSubj: 'lnsLeftAxisButton', }; case 'yRight': return { @@ -98,6 +104,7 @@ const popoverConfig = ( : i18n.translate('xpack.lens.xyChart.rightAxisLabel', { defaultMessage: 'Right axis', }), + buttonDataTestSubj: 'lnsRightAxisButton', }; case 'x': default: @@ -111,6 +118,8 @@ const popoverConfig = ( : i18n.translate('xpack.lens.xyChart.bottomAxisLabel', { defaultMessage: 'Bottom axis', }), + + buttonDataTestSubj: 'lnsBottomAxisButton', }; } }; @@ -143,6 +152,7 @@ export const AxisSettingsPopover: React.FunctionComponent 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 4aa5bd62c05a..c7781c2e1d50 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 @@ -208,6 +208,7 @@ export function XyToolbar(props: VisualizationToolbarProps) { isDisabled={!hasNonBarSeries} type="values" groupPosition="left" + buttonDataTestSubj="lnsMissingValuesButton" > ) { })} > { return { @@ -488,6 +490,7 @@ const ColorPicker = ({ const colorPicker = ( lns-empty-dimension', operation: 'avg', field: 'bytes', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', operation: 'terms', field: 'ip', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.save(title, saveAsNew, redirectToOrigin); } diff --git a/x-pack/test/functional/apps/lens/dashboard.ts b/x-pack/test/functional/apps/lens/dashboard.ts index 4a68c9a8ff3f..fa13d013ea11 100644 --- a/x-pack/test/functional/apps/lens/dashboard.ts +++ b/x-pack/test/functional/apps/lens/dashboard.ts @@ -51,7 +51,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await find.clickByButtonText('lnsXYvis'); await dashboardAddPanel.closeAddPanel(); await PageObjects.lens.goToTimeRange(); - await clickInChart(5, 5); // hardcoded position of bar + await clickInChart(5, 5); // hardcoded position of bar, depends heavy on data and charts implementation await retry.try(async () => { await testSubjects.click('applyFiltersPopoverButton'); @@ -68,5 +68,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const hasIpFilter = await filterBar.hasFilter('ip', '97.220.3.248'); expect(hasIpFilter).to.be(true); }); + it('should be able to add filters by clicking in pie chart', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.clickOpenAddPanel(); + await dashboardAddPanel.filterEmbeddableNames('lnsPieVis'); + await find.clickByButtonText('lnsPieVis'); + await dashboardAddPanel.closeAddPanel(); + + await PageObjects.lens.goToTimeRange(); + await clickInChart(5, 5); // hardcoded position of the slice, depends heavy on data and charts implementation + + await PageObjects.lens.assertExactText( + '[data-test-subj="embeddablePanelHeading-lnsPieVis"]', + 'lnsPieVis' + ); + const hasGeoDestFilter = await filterBar.hasFilter('geo.dest', 'LS'); + expect(hasGeoDestFilter).to.be(true); + }); }); } diff --git a/x-pack/test/functional/apps/lens/rollup.ts b/x-pack/test/functional/apps/lens/rollup.ts index f6882c8aed21..8e1dc231b6b1 100644 --- a/x-pack/test/functional/apps/lens/rollup.ts +++ b/x-pack/test/functional/apps/lens/rollup.ts @@ -34,18 +34,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { operation: 'date_histogram', field: '@timestamp', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', operation: 'sum', field: 'bytes', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', operation: 'terms', field: 'geo.src', }); + await PageObjects.lens.closeDimensionEditor(); expect(await find.allByCssSelector('.echLegendItem')).to.have.length(2); await PageObjects.lens.save('Afancilenstest'); diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index 8c4321d77acf..42807a23cb13 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -8,20 +8,12 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['visualize', 'lens']); + const PageObjects = getPageObjects(['visualize', 'lens', 'common']); const find = getService('find'); const listingTable = getService('listingTable'); const testSubjects = getService('testSubjects'); 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 PageObjects.lens.assertMetric('Maximum of bytes', '19,986'); - }); - it('should allow creation of lens xy chart', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); @@ -32,18 +24,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { operation: 'date_histogram', field: '@timestamp', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', operation: 'avg', field: 'bytes', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', operation: 'terms', field: '@message.raw', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.switchToVisualization('lnsDatatable'); await PageObjects.lens.removeDimension('lnsDatatable_column'); @@ -54,6 +49,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { operation: 'terms', field: 'ip', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.save('Afancilenstest'); @@ -70,8 +66,23 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // legend item(s), so we're using a class selector here. expect(await find.allByCssSelector('.echLegendItem')).to.have.length(3); }); + it('should create an xy visualization with filters aggregation', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsXYvis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-dimensionTrigger', + operation: 'filters', + isPreviousIncompatible: true, + }); + await PageObjects.lens.addFilterToAgg(`geo.src : CN`); - it('should allow seamless transition to and from table view', async () => { + expect(await PageObjects.lens.getFiltersAggLabels()).to.eql([`ip : *`, `geo.src : CN`]); + expect(await find.allByCssSelector('.echLegendItem')).to.have.length(2); + }); + + it('should transition from metric to table to metric', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await listingTable.searchForItemWithName('Artistpreviouslyknownaslens'); await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); @@ -84,7 +95,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 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 () => { + it('should transition from a multi-layer stacked bar to a multi-layer line chart and correctly remove all layers', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); await PageObjects.lens.goToTimeRange(); @@ -95,22 +106,75 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { field: '@timestamp', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', operation: 'avg', field: 'bytes', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.createLayer(); expect(await PageObjects.lens.hasChartSwitchWarning('line')).to.eql(false); await PageObjects.lens.switchToVisualization('line'); + await PageObjects.lens.configureDimension( + { + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'geo.src', + }, + 1 + ); + await PageObjects.lens.closeDimensionEditor(); + await PageObjects.lens.configureDimension( + { + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'avg', + field: 'bytes', + }, + 1 + ); + + await PageObjects.lens.closeDimensionEditor(); expect(await PageObjects.lens.getLayerCount()).to.eql(2); + await testSubjects.click('lnsLayerRemove'); + await testSubjects.click('lnsLayerRemove'); + await testSubjects.existOrFail('empty-workspace'); }); - it('should switch from a multi-layer stacked bar to donut chart using suggestions', async () => { + it('should edit settings of xy line chart', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsXYvis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis'); + await PageObjects.lens.goToTimeRange(); + await testSubjects.click('lnsXY_splitDimensionPanel > indexPattern-dimension-remove'); + await PageObjects.lens.switchToVisualization('line'); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'max', + field: 'memory', + }); + await PageObjects.lens.editDimensionLabel('Test of label'); + await PageObjects.lens.editDimensionFormat('Percent'); + await PageObjects.lens.editDimensionColor('#ff0000'); + await PageObjects.lens.editMissingValues('Linear'); + + await PageObjects.lens.assertMissingValues('Linear'); + await PageObjects.lens.assertColor('#ff0000'); + + await testSubjects.existOrFail('indexPattern-dimension-formatDecimals'); + + await PageObjects.lens.closeDimensionEditor(); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + 'Test of label' + ); + }); + + it('should transition from a multi-layer stacked bar to donut chart using suggestions', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); await PageObjects.lens.goToTimeRange(); @@ -121,12 +185,14 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { field: 'geo.dest', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension({ dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', operation: 'avg', field: 'bytes', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.createLayer(); await PageObjects.lens.configureDimension( @@ -138,6 +204,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 1 ); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension( { dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', @@ -146,6 +213,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }, 1 ); + + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.save('twolayerchart'); await testSubjects.click('lnsSuggestion-asDonut > lnsSuggestion'); @@ -158,7 +227,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - it('should allow transition from line chart to donut chart and to bar chart', async () => { + it('should 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'); @@ -185,7 +254,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - it('should allow seamless transition from bar chart to line chart using layer chart switch', async () => { + it('should 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'); @@ -203,7 +272,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - it('should allow seamless transition from pie chart to treemap chart', async () => { + it('should transition from pie chart to treemap chart', async () => { await PageObjects.visualize.gotoVisualizationLandingPage(); await listingTable.searchForItemWithName('lnsPieVis'); await PageObjects.lens.clickVisualizeListItemTitle('lnsPieVis'); @@ -221,7 +290,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); - it('should allow creating a pie chart and switching to datatable', async () => { + it('should create a pie chart and switch to datatable', async () => { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVisType('lens'); await PageObjects.lens.goToTimeRange(); @@ -231,6 +300,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { operation: 'date_histogram', field: '@timestamp', }); + await PageObjects.lens.closeDimensionEditor(); await PageObjects.lens.configureDimension({ dimension: 'lnsPie_sizeByDimensionPanel > lns-empty-dimension', @@ -238,6 +308,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { field: 'bytes', }); + await PageObjects.lens.closeDimensionEditor(); expect(await PageObjects.lens.hasChartSwitchWarning('lnsDatatable')).to.eql(false); await PageObjects.lens.switchToVisualization('lnsDatatable'); diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index e3c21085b92d..a1e62afbe14c 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -13,7 +13,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont const retry = getService('retry'); const find = getService('find'); const comboBox = getService('comboBox'); - const PageObjects = getPageObjects(['header', 'header', 'timePicker']); + const PageObjects = getPageObjects(['header', 'header', 'timePicker', 'common']); return logWrapper('lensPage', log, { /** @@ -85,19 +85,32 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont * @param layerIndex - the index of the layer */ async configureDimension( - opts: { dimension: string; operation: string; field: string }, + opts: { + dimension: string; + operation: string; + field?: string; + isPreviousIncompatible?: boolean; + }, layerIndex = 0 ) { await retry.try(async () => { await testSubjects.click(`lns-layerPanel-${layerIndex} > ${opts.dimension}`); await testSubjects.exists(`lns-indexPatternDimension-${opts.operation}`); }); + const operationSelector = opts.isPreviousIncompatible + ? `lns-indexPatternDimension-${opts.operation} incompatible` + : `lns-indexPatternDimension-${opts.operation}`; + await testSubjects.click(operationSelector); - await testSubjects.click(`lns-indexPatternDimension-${opts.operation}`); + if (opts.field) { + const target = await testSubjects.find('indexPattern-dimension-field'); + await comboBox.openOptionsList(target); + await comboBox.setElement(target, opts.field); + } + }, - const target = await testSubjects.find('indexPattern-dimension-field'); - await comboBox.openOptionsList(target); - await comboBox.setElement(target, opts.field); + // closes the dimension editor flyout + async closeDimensionEditor() { await testSubjects.click('lns-indexPattern-dimensionContainerTitle'); }, @@ -107,7 +120,17 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont async removeDimension(dimensionTestSubj: string) { await testSubjects.click(`${dimensionTestSubj} > indexPattern-dimension-remove`); }, - + /** + * adds new filter to filters agg + */ + async addFilterToAgg(queryString: string) { + await testSubjects.click('lns-newBucket-add'); + const queryInput = await testSubjects.find('indexPattern-filters-queryStringInput'); + await queryInput.type(queryString); + await PageObjects.common.pressEnterKey(); + await PageObjects.common.pressEnterKey(); + await PageObjects.common.sleep(1000); // give time for debounced components to rerender + }, /** * Save the current Lens visualization. */ @@ -141,10 +164,43 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await testSubjects.click('lnsApp_saveAndReturnButton'); }, + async editDimensionLabel(label: string) { + await testSubjects.setValue('indexPattern-label-edit', label); + }, + async editDimensionFormat(format: string) { + const formatInput = await testSubjects.find('indexPattern-dimension-format'); + await comboBox.openOptionsList(formatInput); + await comboBox.setElement(formatInput, format); + }, + async editDimensionColor(color: string) { + const colorPickerInput = await testSubjects.find('colorPickerAnchor'); + await colorPickerInput.type(color); + await PageObjects.common.sleep(1000); // give time for debounced components to rerender + }, + async editMissingValues(option: string) { + await retry.try(async () => { + await testSubjects.click('lnsMissingValuesButton'); + await testSubjects.exists('lnsMissingValuesSelect'); + }); + await testSubjects.click('lnsMissingValuesSelect'); + const optionSelector = await find.byCssSelector(`#${option}`); + await optionSelector.click(); + }, + getTitle() { return testSubjects.getVisibleText('lns_ChartTitle'); }, + async getFiltersAggLabels() { + const labels = []; + const filters = await testSubjects.findAll('indexPattern-filters-existingFilterContainer'); + for (let i = 0; i < filters.length; i++) { + labels.push(await filters[i].getVisibleText()); + } + log.debug(`Found ${labels.length} filters on current page`); + return labels; + }, + /** * Uses the Lens visualization switcher to switch visualizations. * @@ -275,5 +331,13 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont await this.assertExactText('[data-test-subj="lns_metric_title"]', title); await this.assertExactText('[data-test-subj="lns_metric_value"]', count); }, + + async assertMissingValues(option: string) { + await this.assertExactText('[data-test-subj="lnsMissingValuesSelect"]', option); + }, + async assertColor(color: string) { + // TODO: target dimensionTrigger color element after merging https://github.com/elastic/kibana/pull/76871 + await testSubjects.getAttribute('colorPickerAnchor', color); + }, }); }