diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx index 353bb85dd32e1..aa39a09427008 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx @@ -133,6 +133,7 @@ function DatatableComponent(props: DatatableProps & { formatFactory: FormatFacto return ( { return { diff --git a/x-pack/legacy/plugins/lens/public/drag_drop/drag_drop.tsx b/x-pack/legacy/plugins/lens/public/drag_drop/drag_drop.tsx index e1f765ae6e673..bf3f207a1d7d5 100644 --- a/x-pack/legacy/plugins/lens/public/drag_drop/drag_drop.tsx +++ b/x-pack/legacy/plugins/lens/public/drag_drop/drag_drop.tsx @@ -56,7 +56,7 @@ interface Props { /** * The optional test subject associated with this DOM element. */ - dataTestSubj?: string; + 'data-test-subj'?: string; } /** @@ -128,7 +128,7 @@ export function DragDrop(props: Props) { return (
); } + function getTopSuggestion( props: Props, visualizationId: string, newVisualization: Visualization ): Suggestion | undefined { - return getSuggestions({ + const suggestions = getSuggestions({ datasourceMap: props.datasourceMap, datasourceStates: props.datasourceStates, visualizationMap: { [visualizationId]: newVisualization }, @@ -251,5 +252,14 @@ function getTopSuggestion( // don't use extended versions of current data table on switching between visualizations // to avoid confusing the user. return suggestion.changeType !== 'extended'; - })[0]; + }); + + // We prefer unchanged or reduced suggestions when switching + // charts since that allows you to switch from A to B and back + // to A with the greatest chance of preserving your original state. + return ( + suggestions.find(s => s.changeType === 'unchanged') || + suggestions.find(s => s.changeType === 'reduced') || + suggestions[0] + ); } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx index 2faa2ed902b5b..2884811e7faf8 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/editor_frame.test.tsx @@ -1257,7 +1257,10 @@ describe('editor_frame', () => { instance.update(); act(() => { - instance.find('[data-test-subj="lnsDragDrop"]').simulate('drop'); + instance + .find('[data-test-subj="lnsWorkspace"]') + .last() + .simulate('drop'); }); expect(mockVisualization.renderConfigPanel).toHaveBeenCalledWith( diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx index cc7b7148adae9..4db1997ccfd39 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/editor_frame/workspace_panel.tsx @@ -163,7 +163,12 @@ export function InnerWorkspacePanel({ } return ( - + {renderVisualization()} ); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx index 403b76ed1c49c..0afc769688218 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx @@ -44,7 +44,7 @@ export function FieldItem({ field, indexPattern, highlight, exists }: FieldItemP return ( { - const columnOrder = (updatedLayer || state.layers[layerId]).columnOrder; - const columnMap = (updatedLayer || state.layers[layerId]).columns; + const updatedState = updatedLayer + ? { + ...state, + layers: { + ...state.layers, + [layerId]: updatedLayer, + }, + } + : state; + // It's fairly easy to accidentally introduce a mismatch between + // columnOrder and columns, so this is a safeguard to ensure the + // two match up. + const layers = _.mapValues(updatedState.layers, layer => ({ + ...layer, + columns: _.pick, Record>( + layer.columns, + layer.columnOrder + ), + })); + + const columnOrder = layers[layerId].columnOrder; + const columnMap = layers[layerId].columns; const isMultiRow = Object.values(columnMap).some(column => column.isBucketed); return { - state: updatedLayer - ? { - ...state, - layers: { - ...state.layers, - [layerId]: updatedLayer, - }, - } - : state, + state: { + ...updatedState, + layers, + }, table: { columns: columnOrder.map(columnId => ({ @@ -410,9 +430,11 @@ function createAlternativeMetricSuggestions( field, suggestedPriority: undefined, }); - const updatedLayer = buildLayerByColumnOrder({ ...layer, columns: { [newId]: newColumn } }, [ - newId, - ]); + const updatedLayer = { + ...layer, + columns: { [newId]: newColumn }, + columnOrder: [newId], + }; suggestions.push( buildSuggestion({ state, @@ -441,10 +463,11 @@ function createSuggestionWithDefaultDateHistogram( field: indexPattern.fields.find(({ name }) => name === indexPattern.timeFieldName), suggestedPriority: undefined, }); - const updatedLayer = buildLayerByColumnOrder( - { ...layer, columns: { ...layer.columns, [newId]: timeColumn } }, - [...buckets, newId, ...metrics] - ); + const updatedLayer = { + ...layer, + columns: { ...layer.columns, [newId]: timeColumn }, + columnOrder: [...buckets, newId, ...metrics], + }; return buildSuggestion({ state, layerId, @@ -465,15 +488,15 @@ function createSimplifiedTableSuggestions(state: IndexPatternPrivateState, layer availableBucketedColumns.map((_col, index) => { // build suggestions with fewer buckets const bucketedColumns = availableBucketedColumns.slice(0, index + 1); - const allMetricsSuggestion = buildLayerByColumnOrder(layer, [ - ...bucketedColumns, - ...availableMetricColumns, - ]); + const allMetricsSuggestion = { + ...layer, + columnOrder: [...bucketedColumns, ...availableMetricColumns], + }; if (availableMetricColumns.length > 1) { return [ allMetricsSuggestion, - buildLayerByColumnOrder(layer, [...bucketedColumns, availableMetricColumns[0]]), + { ...layer, columnOrder: [...bucketedColumns, availableMetricColumns[0]] }, ]; } else { return allMetricsSuggestion; @@ -483,7 +506,7 @@ function createSimplifiedTableSuggestions(state: IndexPatternPrivateState, layer .concat( availableMetricColumns.map(columnId => { // build suggestions with only metrics - return buildLayerByColumnOrder(layer, [columnId]); + return { ...layer, columnOrder: [columnId] }; }) ) .map(updatedLayer => { @@ -516,14 +539,3 @@ function getMetricSuggestionTitle(layer: IndexPatternLayer, onlyMetric: boolean) function separateBucketColumns(layer: IndexPatternLayer) { return partition(layer.columnOrder, columnId => layer.columns[columnId].isBucketed); } - -function buildLayerByColumnOrder( - layer: IndexPatternLayer, - columnOrder: string[] -): IndexPatternLayer { - return { - ...layer, - columns: _.pick(layer.columns, columnOrder), - columnOrder, - }; -} diff --git a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx index 53c4a83cf9c2a..f942206e4b70b 100644 --- a/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx +++ b/x-pack/legacy/plugins/lens/public/metric_visualization_plugin/metric_expression.test.tsx @@ -52,42 +52,44 @@ describe('metric_expression', () => { expect(shallow( x as FieldFormat} />)) .toMatchInlineSnapshot(` -
- -
- 10110 -
-
- My fanci metric chart -
-
-
- `); +
+ +
+ 10110 +
+
+ My fanci metric chart +
+
+
+ `); }); test('it does not render title in reduced mode', () => { @@ -115,10 +117,9 @@ describe('metric_expression', () => { } } > - +
- -
{value}
- {mode === 'full' &&
{title}
} + +
+ {value} +
+ {mode === 'full' && ( +
+ {title} +
+ )}
); diff --git a/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts b/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts index 19f313f2d56d6..47ab38de7adf8 100644 --- a/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts +++ b/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts @@ -8,11 +8,9 @@ import { i18n } from '@kbn/i18n'; import { setup } from '../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy'; import { BASE_APP_URL, getEditPath } from '../common'; -const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; - setup.types.visTypeAliasRegistry.add({ aliasUrl: BASE_APP_URL, - name: NOT_INTERNATIONALIZED_PRODUCT_NAME, + name: 'lens', title: i18n.translate('xpack.lens.visTypeAlias.title', { defaultMessage: 'Lens Visualizations', }), diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx index 07ce118673bd1..4d0c7b7044163 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_config_panel.tsx @@ -214,6 +214,7 @@ export function XYConfigPanel(props: VisualizationProps) { { @@ -28,7 +28,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { describe('', function() { this.tags(['ciGroup4', 'skipFirefox']); - loadTestFile(require.resolve('./indexpattern_datapanel')); + loadTestFile(require.resolve('./smokescreen')); }); }); } diff --git a/x-pack/test/functional/apps/lens/indexpattern_datapanel.ts b/x-pack/test/functional/apps/lens/indexpattern_datapanel.ts deleted file mode 100644 index 4c89ef4d51cc9..0000000000000 --- a/x-pack/test/functional/apps/lens/indexpattern_datapanel.ts +++ /dev/null @@ -1,40 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function({ getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['header', 'common', 'lens']); - - describe('indexpattern_datapanel', () => { - beforeEach(async () => { - await PageObjects.common.navigateToApp('lens'); - }); - - it('should list the index pattern fields', async () => { - await PageObjects.lens.openIndexPatternFiltersPopover(); - await PageObjects.lens.toggleExistenceFilter(); - - const fields = await PageObjects.lens.findAllFields(); - const fieldText = await Promise.all(fields.map(field => field.getVisibleText())); - expect(fieldText).to.eql([ - '_score', - '@timestamp', - 'bytes', - 'id', - 'machine.ram', - 'memory', - 'meta.user.lastname', - 'phpmemory', - 'relatedContent.article:modified_time', - 'relatedContent.article:published_time', - 'utc_time', - ]); - }); - }); -} diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts new file mode 100644 index 0000000000000..1abd137659d91 --- /dev/null +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -0,0 +1,111 @@ +/* + * 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'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects([ + 'header', + 'common', + 'visualize', + 'dashboard', + 'header', + 'timePicker', + 'lens', + ]); + const find = getService('find'); + const dashboardAddPanel = getService('dashboardAddPanel'); + + async function assertExpectedMetric() { + await PageObjects.lens.assertExactText( + '[data-test-subj="lns_metric_title"]', + 'Maximum of bytes' + ); + await PageObjects.lens.assertExactText('[data-test-subj="lns_metric_value"]', '19,986'); + } + + 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"] tbody .euiTableCellContent__text', + '19,986' + ); + } + + describe('lens smokescreen tests', () => { + it('should allow editing saved visualizations', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); + await PageObjects.lens.goToTimeRange(); + await assertExpectedMetric(); + }); + + it('should be embeddable in dashboards', async () => { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.clickOpenAddPanel(); + await find.clickByButtonText('Artistpreviouslyknownaslens'); + await dashboardAddPanel.closeAddPanel(); + await PageObjects.lens.goToTimeRange(); + await assertExpectedMetric(); + }); + + it('should allow seamless transition to and from table view', async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await PageObjects.lens.clickVisualizeListItemTitle('Artistpreviouslyknownaslens'); + await PageObjects.lens.goToTimeRange(); + await assertExpectedMetric(); + await PageObjects.lens.switchToVisualization('lnsChartSwitchPopover_lnsDatatable'); + await assertExpectedTable(); + await PageObjects.lens.switchToVisualization('lnsChartSwitchPopover_lnsMetric'); + await assertExpectedMetric(); + }); + + it('should allow creation of lens visualizations', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.toggleExistenceFilter(); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: + '[data-test-subj="lnsXY_xDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]', + operation: 'date_histogram', + }); + + await PageObjects.lens.configureDimension({ + dimension: + '[data-test-subj="lnsXY_yDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]', + operation: 'avg', + field: 'bytes', + }); + + await PageObjects.lens.setTitle('Afancilenstest'); + + await PageObjects.lens.save(); + + // Ensure the visualization shows up in the visualize list, and takes + // us back to the visualization as we configured it. + await PageObjects.visualize.gotoVisualizationLandingPage(); + await PageObjects.lens.clickVisualizeListItemTitle('Afancilenstest'); + await PageObjects.lens.goToTimeRange(); + + expect(await PageObjects.lens.getTitle()).to.eql('Afancilenstest'); + + // .echLegendItem__title is the only viable way of getting the xy chart's + // legend item(s), so we're using a class selector here. + await PageObjects.lens.assertExpectedText( + '.echLegendItem__title', + legendText => !!legendText && legendText.includes('Average of bytes') + ); + }); + }); +} 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 new file mode 100644 index 0000000000000..992bf7c85e9fd Binary files /dev/null and b/x-pack/test/functional/es_archives/lens/basic/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/lens/basic/mappings.json b/x-pack/test/functional/es_archives/lens/basic/mappings.json new file mode 100644 index 0000000000000..10a94d305dd5d --- /dev/null +++ b/x-pack/test/functional/es_archives/lens/basic/mappings.json @@ -0,0 +1,1155 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "ecc01e367a369542bc2b15dae1fb1773", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "b2d549df61fd5bf8098427ec68a4712d", + "apm-telemetry": "07ee1939fa4302c62ddc052ec03fed90", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "config": "87aca8fdb053154f11383fce3dbf3edf", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d69713426be87ba23283776aab149b9a", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "a4229f8b16a6820c6d724b7e0c1f729d", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-ui-timeline": "1f6f0860ad7bc0dba3e42467ca40470d", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "25de8c2deec044392922989cfcf24c54", + "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "description": { + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "alertTypeParams": { + "enabled": false, + "type": "object" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "interval": { + "type": "keyword" + }, + "scheduledTaskId": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "accessibility:disableAnimations": { + "type": "boolean" + }, + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "defaultIndex": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "gis-map": { + "properties": { + "bounds": { + "strategy": "recursive", + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "mapsTotalCount": { + "type": "long" + }, + "timeCaptured": { + "type": "date" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "spaceId": { + "type": "keyword" + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "dynamic": "true", + "properties": { + "indexName": { + "type": "keyword" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index e22e36fcd73aa..4163e77911934 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -44,7 +44,7 @@ import { SnapshotRestorePageProvider } from './snapshot_restore_page'; import { CrossClusterReplicationPageProvider } from './cross_cluster_replication_page'; import { RemoteClustersPageProvider } from './remote_clusters_page'; import { CopySavedObjectsToSpacePageProvider } from './copy_saved_objects_to_space_page'; -import { LensPageProvider } from './lens'; +import { LensPageProvider } from './lens_page'; // just like services, PageObjects are defined as a map of // names to Providers. Merge in Kibana's or pick specific ones diff --git a/x-pack/test/functional/page_objects/lens.ts b/x-pack/test/functional/page_objects/lens.ts deleted file mode 100644 index fa762dc86fd8d..0000000000000 --- a/x-pack/test/functional/page_objects/lens.ts +++ /dev/null @@ -1,25 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FtrProviderContext } from '../ftr_provider_context'; - -export function LensPageProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - - return { - async openIndexPatternFiltersPopover() { - await testSubjects.click('lnsIndexPatternFiltersToggle'); - }, - - async toggleExistenceFilter() { - await testSubjects.click('lnsEmptyFilter'); - }, - - async findAllFields() { - return await testSubjects.findAll('lnsFieldListPanelField'); - }, - }; -} diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts new file mode 100644 index 0000000000000..1825876782d15 --- /dev/null +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -0,0 +1,139 @@ +/* + * 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 { FtrProviderContext } from '../ftr_provider_context'; +import { logWrapper } from './log_wrapper'; + +export function LensPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const find = getService('find'); + const PageObjects = getPageObjects([ + 'header', + 'common', + 'visualize', + 'dashboard', + 'header', + 'timePicker', + ]); + + return logWrapper('lensPage', log, { + /** + * Clicks the index pattern filters toggle. + */ + async toggleIndexPatternFiltersPopover() { + await testSubjects.click('lnsIndexPatternFiltersToggle'); + }, + + /** + * Toggles the field existence checkbox. + */ + async toggleExistenceFilter() { + await this.toggleIndexPatternFiltersPopover(); + await testSubjects.click('lnsEmptyFilter'); + await this.toggleIndexPatternFiltersPopover(); + }, + + async findAllFields() { + return await testSubjects.findAll('lnsFieldListPanelField'); + }, + + /** + * Move the date filter to the specified time range, defaults to + * a range that has data in our dataset. + */ + goToTimeRange(fromTime = '2015-09-19 06:31:44.000', toTime = '2015-09-23 18:31:44.000') { + return PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + }, + + /** + * Wait for the specified element to have text that passes the specified test. + * + * @param selector - the element selector + * @param test - the test function to run on the element's text + */ + async assertExpectedText(selector: string, test: (value?: string) => boolean) { + let actualText: string | undefined; + + await retry.waitForWithTimeout('assertExpectedText', 1000, async () => { + actualText = await find.byCssSelector(selector).then(el => el.getVisibleText()); + return test(actualText); + }); + + if (!test(actualText)) { + throw new Error(`"${actualText}" did not match expectation.`); + } + }, + + /** + * Asserts that the specified element has the expected inner text. + * + * @param selector - the element selector + * @param expectedText - the expected text + */ + assertExactText(selector: string, expectedText: string) { + return this.assertExpectedText(selector, value => value === expectedText); + }, + + /** + * Uses the Lens visualization switcher to switch visualizations. + * + * @param dataTestSubj - the data-test-subj of the visualization to switch to + */ + async switchToVisualization(dataTestSubj: string) { + await testSubjects.click('lnsChartSwitchPopover'); + await testSubjects.click(dataTestSubj); + }, + + /** + * Clicks a visualize list item's title (in the visualize app). + * + * @param title - the title of the list item to be clicked + */ + clickVisualizeListItemTitle(title: string) { + return testSubjects.click(`visListingTitleLink-${title}`); + }, + + /** + * Changes the specified dimension to the specified operation and (optinally) field. + * + * @param opts.from - the text of the dimension being changed + * @param opts.to - the desired operation for the dimension + * @param opts.field - the desired field for the dimension + */ + async configureDimension(opts: { dimension: string; operation?: string; field?: string }) { + await find.clickByCssSelector(opts.dimension); + + if (opts.operation) { + await find.clickByCssSelector( + `[data-test-subj="lns-indexPatternDimensionIncompatible-${opts.operation}"], + [data-test-subj="lns-indexPatternDimension-${opts.operation}"]` + ); + } + + if (opts.field) { + await testSubjects.click('indexPattern-dimension-field'); + await testSubjects.click(`lns-fieldOption-${opts.field}`); + } + }, + + /** + * Save the current Lens visualization. + */ + save() { + return testSubjects.click('lnsApp_saveButton'); + }, + + setTitle(title: string) { + return testSubjects.setValue('lns_ChartTitle', title); + }, + + getTitle() { + return testSubjects.getAttribute('lns_ChartTitle', 'value'); + }, + }); +} diff --git a/x-pack/test/functional/page_objects/log_wrapper.ts b/x-pack/test/functional/page_objects/log_wrapper.ts new file mode 100644 index 0000000000000..56ab7be81caba --- /dev/null +++ b/x-pack/test/functional/page_objects/log_wrapper.ts @@ -0,0 +1,34 @@ +/* + * 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 { ToolingLog } from '@kbn/dev-utils'; + +/** + * Wraps the specified object instance with debug log statements of all method calls. + * + * @param prefix - The string to prefix to all log messages + * @param log - The logger to use + * @param instance - The object being wrapped + */ +export function logWrapper>( + prefix: string, + log: ToolingLog, + instance: T +): T { + return Object.keys(instance).reduce((acc, prop) => { + const baseFn = acc[prop]; + (acc as Record)[prop] = (...args: unknown[]) => { + logMethodCall(log, prefix, prop, args); + return baseFn.apply(instance, args); + }; + return acc; + }, instance); +} + +function logMethodCall(log: ToolingLog, prefix: string, prop: string, args: unknown[]) { + const argsStr = args.map(arg => (typeof arg === 'string' ? `'${arg}'` : arg)).join(', '); + log.debug(`${prefix}.${prop}(${argsStr})`); +}