diff --git a/.buildkite/ftr_oblt_serverless_configs.yml b/.buildkite/ftr_oblt_serverless_configs.yml index 421f2fe461be3..0b2f3242c3a4c 100644 --- a/.buildkite/ftr_oblt_serverless_configs.yml +++ b/.buildkite/ftr_oblt_serverless_configs.yml @@ -38,6 +38,8 @@ enabled: - x-pack/platform/test/serverless/functional/configs/observability/config.group10.ts - x-pack/platform/test/serverless/functional/configs/observability/config.group11.ts - x-pack/platform/test/serverless/functional/configs/observability/config.group12.ts + - x-pack/platform/test/serverless/functional/configs/observability/config.group13.ts + - x-pack/platform/test/serverless/functional/configs/observability/config.group14.ts - x-pack/platform/test/serverless/functional/configs/observability/config.logs_essentials.group1.ts - x-pack/solutions/observability/test/serverless/functional/configs/config.screenshots.ts - x-pack/solutions/observability/test/serverless/functional/configs/config.telemetry.ts diff --git a/.buildkite/ftr_platform_stateful_configs.yml b/.buildkite/ftr_platform_stateful_configs.yml index f54570a785136..ce6ecc8f92194 100644 --- a/.buildkite/ftr_platform_stateful_configs.yml +++ b/.buildkite/ftr_platform_stateful_configs.yml @@ -232,6 +232,10 @@ enabled: - x-pack/platform/test/functional/apps/lens/group5/config.ts - x-pack/platform/test/functional/apps/lens/group6/config.ts - x-pack/platform/test/functional/apps/lens/group7/config.ts + - x-pack/platform/test/functional/apps/lens/group8/config.ts + - x-pack/platform/test/functional/apps/lens/group9/config.ts + - x-pack/platform/test/functional/apps/lens/group10/config.ts + - x-pack/platform/test/functional/apps/lens/group11/config.ts - x-pack/platform/test/functional/apps/lens/open_in_lens/tsvb/config.ts - x-pack/platform/test/functional/apps/lens/open_in_lens/agg_based/config.ts - x-pack/platform/test/functional/apps/lens/open_in_lens/dashboard/config.ts diff --git a/.buildkite/ftr_search_serverless_configs.yml b/.buildkite/ftr_search_serverless_configs.yml index 6f473d1080df6..2f4f02e951c21 100644 --- a/.buildkite/ftr_search_serverless_configs.yml +++ b/.buildkite/ftr_search_serverless_configs.yml @@ -27,4 +27,6 @@ enabled: - x-pack/platform/test/serverless/functional/configs/search/config.group10.ts - x-pack/platform/test/serverless/functional/configs/search/config.group11.ts - x-pack/platform/test/serverless/functional/configs/search/config.group12.ts + - x-pack/platform/test/serverless/functional/configs/search/config.group13.ts + - x-pack/platform/test/serverless/functional/configs/search/config.group14.ts - x-pack/platform/test/agent_builder_api_integration/configs/config.serverless.ts diff --git a/.buildkite/ftr_security_serverless_configs.yml b/.buildkite/ftr_security_serverless_configs.yml index b9312d2da51a1..7b962dfef30b9 100644 --- a/.buildkite/ftr_security_serverless_configs.yml +++ b/.buildkite/ftr_security_serverless_configs.yml @@ -57,6 +57,8 @@ enabled: - x-pack/platform/test/serverless/functional/configs/security/config.group10.ts - x-pack/platform/test/serverless/functional/configs/security/config.group11.ts - x-pack/platform/test/serverless/functional/configs/security/config.group12.ts + - x-pack/platform/test/serverless/functional/configs/security/config.group13.ts + - x-pack/platform/test/serverless/functional/configs/security/config.group14.ts - x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/detection_engine/actions/trial_license_complete_tier/configs/serverless.config.ts - x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/basic_license_essentials_tier/configs/serverless.config.ts - x-pack/solutions/security/test/security_solution_api_integration/test_suites/detections_response/detection_engine/alerts/trial_license_complete_tier/configs/serverless.config.ts diff --git a/x-pack/platform/test/functional/apps/lens/README.md b/x-pack/platform/test/functional/apps/lens/README.md index 5e87a8b210bdd..91f96147ef77a 100644 --- a/x-pack/platform/test/functional/apps/lens/README.md +++ b/x-pack/platform/test/functional/apps/lens/README.md @@ -1,7 +1,138 @@ -# What are all these groups? +# Lens Functional Tests -These tests take a while so they have been broken up into groups with their own `config.ts` and `index.ts` file, causing each of these groups to be independent bundles of tests which can be run on some worker in CI without taking an incredible amount of time. +This directory contains functional tests for the Lens visualization editor. -Want to change the groups to something more logical? Have fun! Just make sure that each group executes on CI in less than 10 minutes or so. We don't currently have any mechanism for validating this right now, you just need to look at the times in the log output on CI, but we'll be working on tooling for making this information more accessible soon. +## Test Groups Overview -- Kibana Operations \ No newline at end of file +| Group | Description | ~Duration | +|-------|-------------|-----------| +| group1-6 | Core Lens functionality tests | varies | +| group7 | ES\|QL tests | ~10-15 min | +| group8 | LogsDB upgrade scenarios | ~20 min | +| group9 | TSDB upgrade scenarios | ~15 min | +| group10 | LogsDB downgrade scenarios | ~20 min | +| group11 | TSDB downgrade scenarios | ~15 min | + +--- + +## LogsDB & TSDB Scenario-Based Tests + +Groups 8-11 use a **scenario-based testing pattern** to verify Lens works correctly with different Elasticsearch index modes and their combinations. + +### What is LogsDB? + +**LogsDB** is an Elasticsearch index mode optimized for log data. It uses: +- Synthetic `_source` for storage efficiency +- Dimension fields (like `host.name`) for efficient grouping +- Special handling for log-specific patterns + +### What is TSDB? + +**TSDB** (Time Series Database) is an Elasticsearch index mode for metrics/time series data. It features: +- Counter and gauge field types with specific aggregation restrictions +- Downsampling support for data rollup +- Time series dimensions for efficient querying + +--- + +## Scenario Testing Pattern + +### Why Test Multiple Index Combinations? + +Real-world Kibana Data Views often span multiple indices of different types: +- A `logs-*` pattern might match both LogsDB streams and legacy regular indices +- During migrations, old and new index formats coexist +- Users may combine different data sources in a single visualization + +### The 6 LogsDB Scenarios + +Each scenario represents a **Data View configuration** combining different index types: + +| # | Scenario | Indexes in Data View | Purpose | +|---|----------|---------------------|---------| +| 1 | No additional stream/index | `[logsdb_stream]` | Baseline: Pure LogsDB only | +| 2 | No additional + no host.name | `[logsdb_stream_no_host]` | LogsDB without dimension field | +| 3 | + Regular index | `[logsdb_stream, regular_index]` | Mix with standard ES index | +| 4 | + LogsDB stream | `[logsdb_stream, logsdb_index_2]` | Two LogsDB streams | +| 5 | + TSDB stream | `[logsdb_stream, tsdb_index]` | Mix with Time Series DB | +| 6 | + TSDB downsampled | `[logsdb_stream, tsdb_downsampled]` | Mix with downsampled TSDB | + +### The 5 TSDB Scenarios + +| # | Scenario | Indexes in Data View | Purpose | +|---|----------|---------------------|---------| +| 1 | No additional stream/index | `[tsdb_stream]` | Baseline: Pure TSDB only | +| 2 | + Regular index | `[tsdb_stream, regular_index]` | Mix with standard ES index | +| 3 | + Downsampled TSDB | `[tsdb_stream, tsdb_downsampled]` | Mix with downsampled data | +| 4 | + Regular + Downsampled | `[tsdb_stream, regular, downsampled]` | Three-way mix | +| 5 | + Additional TSDB | `[tsdb_stream, tsdb_index_2]` | Two TSDB streams | + +--- + +## Upgrade vs Downgrade Testing + +### Upgrade Scenarios (Groups 8 & 9) + +Tests when a regular data stream is **upgraded** to LogsDB/TSDB: + +``` +Time: ──────────────────────────────────────────→ + [Regular stream data] │ [LogsDB/TSDB data] + ↑ + Upgrade point +``` + +**What's tested**: Lens can visualize data spanning both pre-upgrade (regular) and post-upgrade (specialized) periods. + +### Downgrade Scenarios (Groups 10 & 11) + +Tests when a LogsDB/TSDB stream is **downgraded** to regular: + +``` +Time: ──────────────────────────────────────────→ + [LogsDB/TSDB data] │ [Regular stream data] + ↑ + Downgrade point +``` + +**What's tested**: Lens handles transitions when special features are no longer available for new data. + +--- + +## Technical Challenges Being Tested + +### 1. Field Type Conflicts +LogsDB uses `host.name` as a dimension field with special handling. When mixed with regular indices, Lens must handle the field correctly across both. + +### 2. Aggregation Compatibility +TSDB has restrictions (e.g., counter fields can't use `average`). When a Data View spans LogsDB + TSDB, Lens must enforce the stricter rules. + +### 3. Downsampled Data +TSDB downsampling pre-aggregates data, changing which functions are valid (e.g., `median` shows warnings). Mixing this with other index types tests that Lens properly detects and handles these restrictions. + +### 4. Time Window Transitions +Tests verify data is visible when the time picker spans across upgrade/downgrade boundaries. + +--- + +## Test Multiplication + +Each scenario runs **5 tests** per block: +1. Date histogram chart +2. Date histogram with different date field +3. Annotation layer +4. Annotation layer with alternate time field +5. ES|QL query visualization + +**LogsDB total**: `6 scenarios × 5 tests × 2 blocks = 60 tests + 1 smoke test = 61` +**TSDB total**: `5 scenarios × 2-3 tests × 2 blocks + downsampling + field type tests ≈ 35 tests` + +--- + +## Related Files + +- `tsdb_logsdb_helpers.ts` - Shared utilities for scenario setup and test runners +- `group8/logsdb.ts` - LogsDB upgrade scenarios +- `group9/tsdb.ts` - TSDB upgrade scenarios +- `group10/logsdb_downgrade.ts` - LogsDB downgrade scenarios +- `group11/tsdb_downgrade.ts` - TSDB downgrade scenarios diff --git a/x-pack/platform/test/functional/apps/lens/group10/config.ts b/x-pack/platform/test/functional/apps/lens/group10/config.ts new file mode 100644 index 0000000000000..9c9957e5ff401 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group10/config.ts @@ -0,0 +1,17 @@ +/* + * 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/lens/group10/index.ts b/x-pack/platform/test/functional/apps/lens/group10/index.ts new file mode 100644 index 0000000000000..4174b084083a4 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group10/index.ts @@ -0,0 +1,76 @@ +/* + * 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 { EsArchiver } from '@kbn/es-archiver'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => { + const browser = getService('browser'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const { timePicker } = getPageObjects(['timePicker']); + const config = getService('config'); + let remoteEsArchiver; + + describe('lens app - group 10', () => { + const esArchive = 'x-pack/platform/test/fixtures/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; + before(async () => { + await log.debug('Starting lens before method'); + await browser.setWindowSize(1280, 1200); + await kibanaServer.savedObjects.cleanStandardList(); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); + }); + + after(async () => { + await esArchiver.unload(esArchive); + await timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + loadTestFile(require.resolve('./logsdb_downgrade')); + }); +}; diff --git a/x-pack/platform/test/functional/apps/lens/group10/logsdb_downgrade.ts b/x-pack/platform/test/functional/apps/lens/group10/logsdb_downgrade.ts new file mode 100644 index 0000000000000..3707f57008aa7 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group10/logsdb_downgrade.ts @@ -0,0 +1,329 @@ +/* + * 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 moment from 'moment'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { + type ScenarioIndexes, + getDataMapping, + getDocsGenerator, + setupScenarioRunner, + TIME_PICKER_FORMAT, +} from '../tsdb_logsdb_helpers'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { common, lens, discover, header, timePicker } = getPageObjects([ + 'common', + 'lens', + 'discover', + 'header', + 'timePicker', + ]); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const es = getService('es'); + const log = getService('log'); + const dataStreams = getService('dataStreams'); + const indexPatterns = getService('indexPatterns'); + const esArchiver = getService('esArchiver'); + const monacoEditor = getService('monacoEditor'); + const retry = getService('retry'); + + const createDocs = getDocsGenerator(log, es, 'logsdb'); + + describe('lens logsdb downgrade', function () { + const logsdbIndex = 'kibana_sample_data_logslogsdb'; + const logsdbDataView = logsdbIndex; + const logsdbEsArchive = + 'src/platform/test/functional/fixtures/es_archiver/kibana_sample_data_logs_logsdb'; + const fromTime = 'Apr 16, 2023 @ 00:00:00.000'; + const toTime = 'Jun 16, 2023 @ 00:00:00.000'; + + before(async () => { + log.info(`loading ${logsdbIndex} index...`); + await esArchiver.loadIfNeeded(logsdbEsArchive); + log.info(`creating a data view for "${logsdbDataView}"...`); + await indexPatterns.create( + { + title: logsdbDataView, + timeFieldName: '@timestamp', + }, + { override: true } + ); + log.info(`updating settings to use the "${logsdbDataView}" dataView...`); + await kibanaServer.uiSettings.update({ + 'dateFormat:tz': 'UTC', + defaultIndex: '0ae0bc7a-e4ca-405c-ab67-f2b5913f2a51', + 'timepicker:timeDefaults': `{ "from": "${fromTime}", "to": "${toTime}" }`, + }); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.uiSettings.replace({}); + await timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await es.indices.delete({ index: [logsdbIndex] }); + }); + + describe('Scenarios with changing stream type', () => { + const getScenarios = ( + initialIndex: string + ): Array<{ + name: string; + indexes: ScenarioIndexes[]; + }> => [ + { + name: 'LogsDB stream with no additional stream/index', + indexes: [{ index: initialIndex }], + }, + { + name: 'LogsDB stream with no additional stream/index and no host.name field', + indexes: [ + { + index: `${initialIndex}_no_host`, + removeLogsDBFields: true, + create: true, + mode: 'logsdb', + }, + ], + }, + { + name: 'LogsDB stream with an additional regular index', + indexes: [{ index: initialIndex }, { index: 'regular_index', create: true }], + }, + { + name: 'LogsDB stream with an additional LogsDB stream', + indexes: [ + { index: initialIndex }, + { index: 'logsdb_index_2', create: true, mode: 'logsdb' }, + ], + }, + { + name: 'LogsDB stream with an additional TSDB stream', + indexes: [{ index: initialIndex }, { index: 'tsdb_index', create: true, mode: 'tsdb' }], + }, + { + name: 'LogsDB stream with an additional TSDB stream downsampled', + indexes: [ + { index: initialIndex }, + { index: 'tsdb_index_downsampled', create: true, mode: 'tsdb', downsample: true }, + ], + }, + ]; + + const { runTestsForEachScenario, toTimeForScenarios, fromTimeForScenarios } = + setupScenarioRunner(getService, getPageObjects, getScenarios); + + describe('LogsDB downgraded to regular data stream scenarios', () => { + const logsdbStream = 'logsdb_stream_dowgradable'; + // rollover does not allow to change name, it will just change backing index underneath + const logsdbConvertedToStream = logsdbStream; + + before(async () => { + log.info(`Creating "${logsdbStream}" data stream...`); + await dataStreams.createDataStream( + logsdbStream, + getDataMapping({ mode: 'logsdb' }), + 'logsdb' + ); + + // add some data to the stream + await createDocs(logsdbStream, { isStream: true }, fromTimeForScenarios); + + log.info(`Update settings for "${logsdbStream}" dataView...`); + await kibanaServer.uiSettings.update({ + 'dateFormat:tz': 'UTC', + 'timepicker:timeDefaults': '{ "from": "now-1y", "to": "now" }', + }); + log.info( + `Dowgrade "${logsdbStream}" stream into regular stream "${logsdbConvertedToStream}"...` + ); + + await dataStreams.downgradeStream( + logsdbStream, + getDataMapping({ mode: 'logsdb' }), + 'logsdb' + ); + log.info( + `Add more data to new "${logsdbConvertedToStream}" dataView (no longer LogsDB)...` + ); + // add some more data when upgraded + await createDocs(logsdbConvertedToStream, { isStream: true }, toTimeForScenarios); + }); + + after(async () => { + await dataStreams.deleteDataStream(logsdbConvertedToStream); + }); + + runTestsForEachScenario(logsdbConvertedToStream, 'logsdb', (indexes) => { + it(`should visualize a date histogram chart`, async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + // check that a basic agg on a field works + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes`, + }); + + await lens.waitForVisualization('xyVisChart'); + const data = await lens.getCurrentChartDebugState('xyVisChart'); + const bars = data?.bars![0].bars; + + log.info('Check counter data before the upgrade'); + // check there's some data before the upgrade + expect(bars?.[0].y).to.be.above(0); + log.info('Check counter data after the upgrade'); + // check there's some data after the upgrade + expect(bars?.[bars.length - 1].y).to.be.above(0); + }); + + it(`should visualize a date histogram chart using a different date field`, async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: 'utc_time', + }); + + // check the counter field works + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes`, + }); + + await lens.waitForVisualization('xyVisChart'); + const data = await lens.getCurrentChartDebugState('xyVisChart'); + const bars = data?.bars![0].bars; + + log.info('Check counter data before the upgrade'); + // check there's some data before the upgrade + expect(bars?.[0].y).to.be.above(0); + log.info('Check counter data after the upgrade'); + // check there's some data after the upgrade + expect(bars?.[bars.length - 1].y).to.be.above(0); + }); + + it('should visualize an annotation layer from a logsDB stream', async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: 'utc_time', + }); + + // check the counter field works + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes`, + }); + await lens.createLayer('annotations'); + + await lens.assertLayerCount(2); + // switch to the annotation tab + await lens.ensureLayerTabIsActive(1); + expect( + await ( + await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') + ).getVisibleText() + ).to.eql('Event'); + await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); + await testSubjects.click('lnsXY_annotation_query'); + await lens.configureQueryAnnotation({ + queryString: 'host.name: *', + timeField: '@timestamp', + textDecoration: { type: 'name' }, + extraFields: ['host.name', 'utc_time'], + }); + await lens.closeDimensionEditor(); + + await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); + await lens.removeLayer(1); + }); + + it('should visualize an annotation layer from a logsDB stream using another time field', async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: 'utc_time', + }); + + // check the counter field works + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes`, + }); + await lens.createLayer('annotations'); + + await lens.assertLayerCount(2); + // switch to the annotation tab + await lens.ensureLayerTabIsActive(1); + expect( + await ( + await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') + ).getVisibleText() + ).to.eql('Event'); + await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); + await testSubjects.click('lnsXY_annotation_query'); + await lens.configureQueryAnnotation({ + queryString: 'host.name: *', + timeField: 'utc_time', + textDecoration: { type: 'name' }, + extraFields: ['host.name', '@timestamp'], + }); + await lens.closeDimensionEditor(); + + await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); + await lens.removeLayer(1); + }); + + it('should visualize correctly ES|QL queries based on a LogsDB stream', async () => { + await common.navigateToApp('discover'); + await discover.selectTextBaseLang(); + + // Use the lens page object here also for discover: both use the same timePicker object + await lens.goToTimeRange( + fromTimeForScenarios, + moment + .utc(toTimeForScenarios, TIME_PICKER_FORMAT) + .add(2, 'hour') + .format(TIME_PICKER_FORMAT) + ); + + await header.waitUntilLoadingHasFinished(); + await monacoEditor.setCodeEditorValue( + `from ${indexes + .map(({ index }) => index) + .join(', ')} | stats averageB = avg(bytes) by extension` + ); + await testSubjects.click('querySubmitButton'); + await header.waitUntilLoadingHasFinished(); + await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); + + await header.waitUntilLoadingHasFinished(); + + await retry.waitFor('lens flyout', async () => { + const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased'); + return ( + dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'averageB' + ); + }); + + // go back to Lens to not break the wrapping function + await common.navigateToApp('lens'); + }); + }); + }); + }); + }); +} diff --git a/x-pack/platform/test/functional/apps/lens/group11/config.ts b/x-pack/platform/test/functional/apps/lens/group11/config.ts new file mode 100644 index 0000000000000..9c9957e5ff401 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group11/config.ts @@ -0,0 +1,17 @@ +/* + * 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/lens/group11/index.ts b/x-pack/platform/test/functional/apps/lens/group11/index.ts new file mode 100644 index 0000000000000..bb08750cec27b --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group11/index.ts @@ -0,0 +1,76 @@ +/* + * 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 { EsArchiver } from '@kbn/es-archiver'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => { + const browser = getService('browser'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const { timePicker } = getPageObjects(['timePicker']); + const config = getService('config'); + let remoteEsArchiver; + + describe('lens app - group 11', () => { + const esArchive = 'x-pack/platform/test/fixtures/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; + before(async () => { + await log.debug('Starting lens before method'); + await browser.setWindowSize(1280, 1200); + await kibanaServer.savedObjects.cleanStandardList(); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); + }); + + after(async () => { + await esArchiver.unload(esArchive); + await timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + loadTestFile(require.resolve('./tsdb_downgrade')); + }); +}; diff --git a/x-pack/platform/test/functional/apps/lens/group11/tsdb_downgrade.ts b/x-pack/platform/test/functional/apps/lens/group11/tsdb_downgrade.ts new file mode 100644 index 0000000000000..e12a12d7dea01 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group11/tsdb_downgrade.ts @@ -0,0 +1,241 @@ +/* + * 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 moment from 'moment'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { + type ScenarioIndexes, + TEST_DOC_COUNT, + TIME_PICKER_FORMAT, + getDataMapping, + getDocsGenerator, + setupScenarioRunner, + sumFirstNValues, +} from '../tsdb_logsdb_helpers'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { lens } = getPageObjects(['common', 'lens']); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const es = getService('es'); + const log = getService('log'); + const dataStreams = getService('dataStreams'); + const indexPatterns = getService('indexPatterns'); + const esArchiver = getService('esArchiver'); + + const createDocs = getDocsGenerator(log, es, 'tsdb'); + + describe('lens tsdb downgrade', function () { + const tsdbIndex = 'kibana_sample_data_logstsdb'; + const tsdbDataView = tsdbIndex; + const tsdbEsArchive = + 'src/platform/test/functional/fixtures/es_archiver/kibana_sample_data_logs_tsdb'; + const fromTime = 'Apr 16, 2023 @ 00:00:00.000'; + const toTime = 'Jun 16, 2023 @ 00:00:00.000'; + + before(async () => { + log.info(`loading ${tsdbIndex} index...`); + await esArchiver.loadIfNeeded(tsdbEsArchive); + log.info(`creating a data view for "${tsdbDataView}"...`); + await indexPatterns.create( + { + title: tsdbDataView, + timeFieldName: '@timestamp', + }, + { override: true } + ); + log.info(`updating settings to use the "${tsdbDataView}" dataView...`); + await kibanaServer.uiSettings.update({ + 'dateFormat:tz': 'UTC', + defaultIndex: '0ae0bc7a-e4ca-405c-ab67-f2b5913f2a51', + 'timepicker:timeDefaults': `{ "from": "${fromTime}", "to": "${toTime}" }`, + }); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.uiSettings.replace({}); + await es.indices.delete({ index: [tsdbIndex] }); + }); + + describe('Scenarios with changing stream type', () => { + const getScenarios = ( + initialIndex: string + ): Array<{ + name: string; + indexes: ScenarioIndexes[]; + }> => [ + { + name: 'Dataview with no additional stream/index', + indexes: [{ index: initialIndex }], + }, + { + name: 'Dataview with an additional regular index', + indexes: [ + { index: initialIndex }, + { index: 'regular_index', create: true, removeTSDBFields: true }, + ], + }, + { + name: 'Dataview with an additional downsampled TSDB stream', + indexes: [ + { index: initialIndex }, + { index: 'tsdb_index_2', create: true, mode: 'tsdb', downsample: true }, + ], + }, + { + name: 'Dataview with additional regular index and a downsampled TSDB stream', + indexes: [ + { index: initialIndex }, + { index: 'regular_index', create: true, removeTSDBFields: true }, + { index: 'tsdb_index_2', create: true, mode: 'tsdb', downsample: true }, + ], + }, + { + name: 'Dataview with an additional TSDB stream', + indexes: [{ index: initialIndex }, { index: 'tsdb_index_2', create: true, mode: 'tsdb' }], + }, + ]; + + const { runTestsForEachScenario, toTimeForScenarios, fromTimeForScenarios } = + setupScenarioRunner(getService, getPageObjects, getScenarios); + + describe('TSDB downgraded to regular data stream scenarios', () => { + const tsdbStream = 'tsdb_stream_dowgradable'; + // rollover does not allow to change name, it will just change backing index underneath + const tsdbConvertedToStream = tsdbStream; + + before(async () => { + log.info(`Creating "${tsdbStream}" data stream...`); + await dataStreams.createDataStream(tsdbStream, getDataMapping({ mode: 'tsdb' }), 'tsdb'); + + // add some data to the stream + await createDocs(tsdbStream, { isStream: true }, fromTimeForScenarios); + + log.info(`Update settings for "${tsdbStream}" dataView...`); + await kibanaServer.uiSettings.update({ + 'dateFormat:tz': 'UTC', + 'timepicker:timeDefaults': '{ "from": "now-1y", "to": "now" }', + }); + log.info( + `Dowgrade "${tsdbStream}" stream into regular stream "${tsdbConvertedToStream}"...` + ); + + await dataStreams.downgradeStream(tsdbStream, getDataMapping({ mode: 'tsdb' }), 'tsdb'); + log.info(`Add more data to new "${tsdbConvertedToStream}" dataView (no longer TSDB)...`); + // add some more data when upgraded + await createDocs(tsdbConvertedToStream, { isStream: true }, toTimeForScenarios); + }); + + after(async () => { + await dataStreams.deleteDataStream(tsdbConvertedToStream); + }); + + runTestsForEachScenario(tsdbConvertedToStream, 'tsdb', (indexes) => { + it('should keep TSDB restrictions only if a tsdb stream is in the dataView mix', async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes_counter`, + keepOpen: true, + }); + + expect( + testSubjects.exists(`lns-indexPatternDimension-average incompatible`, { + timeout: 500, + }) + ).to.eql(indexes.some(({ mode }) => mode === 'tsdb')); + await lens.closeDimensionEditor(); + }); + + it(`should visualize a date histogram chart for counter field`, async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + // just check the data is shown + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'count', + }); + + await lens.waitForVisualization('xyVisChart'); + const data = await lens.getCurrentChartDebugState('xyVisChart'); + const bars = data?.bars![0].bars; + const columnsToCheck = bars ? bars.length / 2 : 0; + // due to the flaky nature of exact check here, we're going to relax it + // as long as there's data before and after it is ok + log.info('Check count before the downgrade'); + // Before the upgrade the count is N times the indexes + expect(sumFirstNValues(columnsToCheck, bars)).to.be.greaterThan( + indexes.length * TEST_DOC_COUNT - 1 + ); + log.info('Check count after the downgrade'); + // later there are only documents for the upgraded stream + expect(sumFirstNValues(columnsToCheck, [...(bars ?? [])].reverse())).to.be.greaterThan( + TEST_DOC_COUNT - 1 + ); + }); + + it('should visualize data when moving the time window around the downgrade moment', async () => { + // check after the downgrade + await lens.goToTimeRange( + moment + .utc(fromTimeForScenarios, TIME_PICKER_FORMAT) + .subtract(1, 'hour') + .format(TIME_PICKER_FORMAT), + moment + .utc(fromTimeForScenarios, TIME_PICKER_FORMAT) + .add(1, 'hour') + .format(TIME_PICKER_FORMAT) // consider only new documents + ); + + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'count', + }); + + await lens.waitForVisualization('xyVisChart'); + const dataBefore = await lens.getCurrentChartDebugState('xyVisChart'); + const barsBefore = dataBefore?.bars![0].bars; + expect(barsBefore?.some(({ y }) => y)).to.eql(true); + + // check after the downgrade + await lens.goToTimeRange( + moment + .utc(toTimeForScenarios, TIME_PICKER_FORMAT) + .add(1, 'second') + .format(TIME_PICKER_FORMAT), + moment + .utc(toTimeForScenarios, TIME_PICKER_FORMAT) + .add(2, 'hour') + .format(TIME_PICKER_FORMAT) // consider also new documents + ); + + await lens.waitForVisualization('xyVisChart'); + const dataAfter = await lens.getCurrentChartDebugState('xyVisChart'); + const barsAfter = dataAfter?.bars![0].bars; + expect(barsAfter?.some(({ y }) => y)).to.eql(true); + }); + }); + }); + }); + }); +} diff --git a/x-pack/platform/test/functional/apps/lens/group4/index.ts b/x-pack/platform/test/functional/apps/lens/group4/index.ts index dff0b7071103f..70e43fb2fda6c 100644 --- a/x-pack/platform/test/functional/apps/lens/group4/index.ts +++ b/x-pack/platform/test/functional/apps/lens/group4/index.ts @@ -80,7 +80,5 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext loadTestFile(require.resolve('./show_underlying_data')); // 2m loadTestFile(require.resolve('./show_underlying_data_dashboard')); // 2m 10s loadTestFile(require.resolve('./share')); // 1m 20s - // keep it last in the group - loadTestFile(require.resolve('./tsdb')); // 3m 56s }); }; diff --git a/x-pack/platform/test/functional/apps/lens/group7/index.ts b/x-pack/platform/test/functional/apps/lens/group7/index.ts index 370bddc9837dc..acbedd305e3e7 100644 --- a/x-pack/platform/test/functional/apps/lens/group7/index.ts +++ b/x-pack/platform/test/functional/apps/lens/group7/index.ts @@ -71,7 +71,6 @@ export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext await kibanaServer.savedObjects.cleanStandardList(); }); - loadTestFile(require.resolve('./logsdb')); // 43m loadTestFile(require.resolve('./esql')); }); }; diff --git a/x-pack/platform/test/functional/apps/lens/group8/config.ts b/x-pack/platform/test/functional/apps/lens/group8/config.ts new file mode 100644 index 0000000000000..9c9957e5ff401 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group8/config.ts @@ -0,0 +1,17 @@ +/* + * 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/lens/group8/index.ts b/x-pack/platform/test/functional/apps/lens/group8/index.ts new file mode 100644 index 0000000000000..15376adf958e8 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group8/index.ts @@ -0,0 +1,76 @@ +/* + * 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 { EsArchiver } from '@kbn/es-archiver'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => { + const browser = getService('browser'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const { timePicker } = getPageObjects(['timePicker']); + const config = getService('config'); + let remoteEsArchiver; + + describe('lens app - group 8', () => { + const esArchive = 'x-pack/platform/test/fixtures/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; + before(async () => { + await log.debug('Starting lens before method'); + await browser.setWindowSize(1280, 1200); + await kibanaServer.savedObjects.cleanStandardList(); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); + }); + + after(async () => { + await esArchiver.unload(esArchive); + await timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + loadTestFile(require.resolve('./logsdb')); + }); +}; diff --git a/x-pack/platform/test/functional/apps/lens/group7/logsdb.ts b/x-pack/platform/test/functional/apps/lens/group8/logsdb.ts similarity index 62% rename from x-pack/platform/test/functional/apps/lens/group7/logsdb.ts rename to x-pack/platform/test/functional/apps/lens/group8/logsdb.ts index afac1e84e4775..7fdaa5ef2fb03 100644 --- a/x-pack/platform/test/functional/apps/lens/group7/logsdb.ts +++ b/x-pack/platform/test/functional/apps/lens/group8/logsdb.ts @@ -6,14 +6,12 @@ */ import expect from '@kbn/expect'; -import moment from 'moment'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { type ScenarioIndexes, getDataMapping, getDocsGenerator, setupScenarioRunner, - TIME_PICKER_FORMAT, } from '../tsdb_logsdb_helpers'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -375,212 +373,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); }); - - describe('LogsDB downgraded to regular data stream scenarios', () => { - const logsdbStream = 'logsdb_stream_dowgradable'; - // rollover does not allow to change name, it will just change backing index underneath - const logsdbConvertedToStream = logsdbStream; - - before(async () => { - log.info(`Creating "${logsdbStream}" data stream...`); - await dataStreams.createDataStream( - logsdbStream, - getDataMapping({ mode: 'logsdb' }), - 'logsdb' - ); - - // add some data to the stream - await createDocs(logsdbStream, { isStream: true }, fromTimeForScenarios); - - log.info(`Update settings for "${logsdbStream}" dataView...`); - await kibanaServer.uiSettings.update({ - 'dateFormat:tz': 'UTC', - 'timepicker:timeDefaults': '{ "from": "now-1y", "to": "now" }', - }); - log.info( - `Dowgrade "${logsdbStream}" stream into regular stream "${logsdbConvertedToStream}"...` - ); - - await dataStreams.downgradeStream( - logsdbStream, - getDataMapping({ mode: 'logsdb' }), - 'logsdb' - ); - log.info( - `Add more data to new "${logsdbConvertedToStream}" dataView (no longer LogsDB)...` - ); - // add some more data when upgraded - await createDocs(logsdbConvertedToStream, { isStream: true }, toTimeForScenarios); - }); - - after(async () => { - await dataStreams.deleteDataStream(logsdbConvertedToStream); - }); - - runTestsForEachScenario(logsdbConvertedToStream, 'logsdb', (indexes) => { - it(`should visualize a date histogram chart`, async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - - // check that a basic agg on a field works - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes`, - }); - - await lens.waitForVisualization('xyVisChart'); - const data = await lens.getCurrentChartDebugState('xyVisChart'); - const bars = data?.bars![0].bars; - - log.info('Check counter data before the upgrade'); - // check there's some data before the upgrade - expect(bars?.[0].y).to.be.above(0); - log.info('Check counter data after the upgrade'); - // check there's some data after the upgrade - expect(bars?.[bars.length - 1].y).to.be.above(0); - }); - - it(`should visualize a date histogram chart using a different date field`, async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: 'utc_time', - }); - - // check the counter field works - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes`, - }); - - await lens.waitForVisualization('xyVisChart'); - const data = await lens.getCurrentChartDebugState('xyVisChart'); - const bars = data?.bars![0].bars; - - log.info('Check counter data before the upgrade'); - // check there's some data before the upgrade - expect(bars?.[0].y).to.be.above(0); - log.info('Check counter data after the upgrade'); - // check there's some data after the upgrade - expect(bars?.[bars.length - 1].y).to.be.above(0); - }); - - it('should visualize an annotation layer from a logsDB stream', async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: 'utc_time', - }); - - // check the counter field works - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes`, - }); - await lens.createLayer('annotations'); - - await lens.assertLayerCount(2); - // switch to the annotation tab - await lens.ensureLayerTabIsActive(1); - expect( - await ( - await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') - ).getVisibleText() - ).to.eql('Event'); - await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); - await testSubjects.click('lnsXY_annotation_query'); - await lens.configureQueryAnnotation({ - queryString: 'host.name: *', - timeField: '@timestamp', - textDecoration: { type: 'name' }, - extraFields: ['host.name', 'utc_time'], - }); - await lens.closeDimensionEditor(); - - await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); - await lens.removeLayer(1); - }); - - it('should visualize an annotation layer from a logsDB stream using another time field', async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: 'utc_time', - }); - - // check the counter field works - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes`, - }); - await lens.createLayer('annotations'); - - await lens.assertLayerCount(2); - // switch to the annotation tab - await lens.ensureLayerTabIsActive(1); - expect( - await ( - await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') - ).getVisibleText() - ).to.eql('Event'); - await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); - await testSubjects.click('lnsXY_annotation_query'); - await lens.configureQueryAnnotation({ - queryString: 'host.name: *', - timeField: 'utc_time', - textDecoration: { type: 'name' }, - extraFields: ['host.name', '@timestamp'], - }); - await lens.closeDimensionEditor(); - - await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); - await lens.removeLayer(1); - }); - - it('should visualize correctly ES|QL queries based on a LogsDB stream', async () => { - await common.navigateToApp('discover'); - await discover.selectTextBaseLang(); - - // Use the lens page object here also for discover: both use the same timePicker object - await lens.goToTimeRange( - fromTimeForScenarios, - moment - .utc(toTimeForScenarios, TIME_PICKER_FORMAT) - .add(2, 'hour') - .format(TIME_PICKER_FORMAT) - ); - - await header.waitUntilLoadingHasFinished(); - await monacoEditor.setCodeEditorValue( - `from ${indexes - .map(({ index }) => index) - .join(', ')} | stats averageB = avg(bytes) by extension` - ); - await testSubjects.click('querySubmitButton'); - await header.waitUntilLoadingHasFinished(); - await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); - - await header.waitUntilLoadingHasFinished(); - - await retry.waitFor('lens flyout', async () => { - const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased'); - return ( - dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'averageB' - ); - }); - - // go back to Lens to not break the wrapping function - await common.navigateToApp('lens'); - }); - }); - }); }); }); }); diff --git a/x-pack/platform/test/functional/apps/lens/group9/config.ts b/x-pack/platform/test/functional/apps/lens/group9/config.ts new file mode 100644 index 0000000000000..9c9957e5ff401 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group9/config.ts @@ -0,0 +1,17 @@ +/* + * 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/lens/group9/index.ts b/x-pack/platform/test/functional/apps/lens/group9/index.ts new file mode 100644 index 0000000000000..0d8407d19ecc9 --- /dev/null +++ b/x-pack/platform/test/functional/apps/lens/group9/index.ts @@ -0,0 +1,76 @@ +/* + * 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 { EsArchiver } from '@kbn/es-archiver'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => { + const browser = getService('browser'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const { timePicker } = getPageObjects(['timePicker']); + const config = getService('config'); + let remoteEsArchiver; + + describe('lens app - group 9', () => { + const esArchive = 'x-pack/platform/test/fixtures/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; + before(async () => { + await log.debug('Starting lens before method'); + await browser.setWindowSize(1280, 1200); + await kibanaServer.savedObjects.cleanStandardList(); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); + }); + + after(async () => { + await esArchiver.unload(esArchive); + await timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + loadTestFile(require.resolve('./tsdb')); + }); +}; diff --git a/x-pack/platform/test/functional/apps/lens/group4/tsdb.ts b/x-pack/platform/test/functional/apps/lens/group9/tsdb.ts similarity index 76% rename from x-pack/platform/test/functional/apps/lens/group4/tsdb.ts rename to x-pack/platform/test/functional/apps/lens/group9/tsdb.ts index 227048e3ed967..2a754d246a2a4 100644 --- a/x-pack/platform/test/functional/apps/lens/group4/tsdb.ts +++ b/x-pack/platform/test/functional/apps/lens/group9/tsdb.ts @@ -7,12 +7,10 @@ import expect from '@kbn/expect'; import { partition } from 'lodash'; -import moment from 'moment'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { type ScenarioIndexes, TEST_DOC_COUNT, - TIME_PICKER_FORMAT, getDataMapping, getDocsGenerator, setupScenarioRunner, @@ -461,138 +459,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); }); - - describe('TSDB downgraded to regular data stream scenarios', () => { - const tsdbStream = 'tsdb_stream_dowgradable'; - // rollover does not allow to change name, it will just change backing index underneath - const tsdbConvertedToStream = tsdbStream; - - before(async () => { - log.info(`Creating "${tsdbStream}" data stream...`); - await dataStreams.createDataStream(tsdbStream, getDataMapping({ mode: 'tsdb' }), 'tsdb'); - - // add some data to the stream - await createDocs(tsdbStream, { isStream: true }, fromTimeForScenarios); - - log.info(`Update settings for "${tsdbStream}" dataView...`); - await kibanaServer.uiSettings.update({ - 'dateFormat:tz': 'UTC', - 'timepicker:timeDefaults': '{ "from": "now-1y", "to": "now" }', - }); - log.info( - `Dowgrade "${tsdbStream}" stream into regular stream "${tsdbConvertedToStream}"...` - ); - - await dataStreams.downgradeStream(tsdbStream, getDataMapping({ mode: 'tsdb' }), 'tsdb'); - log.info(`Add more data to new "${tsdbConvertedToStream}" dataView (no longer TSDB)...`); - // add some more data when upgraded - await createDocs(tsdbConvertedToStream, { isStream: true }, toTimeForScenarios); - }); - - after(async () => { - await dataStreams.deleteDataStream(tsdbConvertedToStream); - }); - - runTestsForEachScenario(tsdbConvertedToStream, 'tsdb', (indexes) => { - it('should keep TSDB restrictions only if a tsdb stream is in the dataView mix', async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes_counter`, - keepOpen: true, - }); - - expect( - testSubjects.exists(`lns-indexPatternDimension-average incompatible`, { - timeout: 500, - }) - ).to.eql(indexes.some(({ mode }) => mode === 'tsdb')); - await lens.closeDimensionEditor(); - }); - - it(`should visualize a date histogram chart for counter field`, async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - // just check the data is shown - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'count', - }); - - await lens.waitForVisualization('xyVisChart'); - const data = await lens.getCurrentChartDebugState('xyVisChart'); - const bars = data?.bars![0].bars; - const columnsToCheck = bars ? bars.length / 2 : 0; - // due to the flaky nature of exact check here, we're going to relax it - // as long as there's data before and after it is ok - log.info('Check count before the downgrade'); - // Before the upgrade the count is N times the indexes - expect(sumFirstNValues(columnsToCheck, bars)).to.be.greaterThan( - indexes.length * TEST_DOC_COUNT - 1 - ); - log.info('Check count after the downgrade'); - // later there are only documents for the upgraded stream - expect(sumFirstNValues(columnsToCheck, [...(bars ?? [])].reverse())).to.be.greaterThan( - TEST_DOC_COUNT - 1 - ); - }); - - it('should visualize data when moving the time window around the downgrade moment', async () => { - // check after the downgrade - await lens.goToTimeRange( - moment - .utc(fromTimeForScenarios, TIME_PICKER_FORMAT) - .subtract(1, 'hour') - .format(TIME_PICKER_FORMAT), - moment - .utc(fromTimeForScenarios, TIME_PICKER_FORMAT) - .add(1, 'hour') - .format(TIME_PICKER_FORMAT) // consider only new documents - ); - - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'count', - }); - - await lens.waitForVisualization('xyVisChart'); - const dataBefore = await lens.getCurrentChartDebugState('xyVisChart'); - const barsBefore = dataBefore?.bars![0].bars; - expect(barsBefore?.some(({ y }) => y)).to.eql(true); - - // check after the downgrade - await lens.goToTimeRange( - moment - .utc(toTimeForScenarios, TIME_PICKER_FORMAT) - .add(1, 'second') - .format(TIME_PICKER_FORMAT), - moment - .utc(toTimeForScenarios, TIME_PICKER_FORMAT) - .add(2, 'hour') - .format(TIME_PICKER_FORMAT) // consider also new documents - ); - - await lens.waitForVisualization('xyVisChart'); - const dataAfter = await lens.getCurrentChartDebugState('xyVisChart'); - const barsAfter = dataAfter?.bars![0].bars; - expect(barsAfter?.some(({ y }) => y)).to.eql(true); - }); - }); - }); }); }); } diff --git a/x-pack/platform/test/serverless/functional/configs/observability/config.group13.ts b/x-pack/platform/test/serverless/functional/configs/observability/config.group13.ts new file mode 100644 index 0000000000000..9662b2d8192dc --- /dev/null +++ b/x-pack/platform/test/serverless/functional/configs/observability/config.group13.ts @@ -0,0 +1,20 @@ +/* + * 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 baseTestConfig = await readConfigFile(require.resolve('../../config.oblt.base.ts')); + + return { + ...baseTestConfig.getAll(), + testFiles: [require.resolve('../../test_suites/visualizations/group7')], + junit: { + reportName: 'Serverless Observability Functional Tests - Common Group 13', + }, + }; +} diff --git a/x-pack/platform/test/serverless/functional/configs/observability/config.group14.ts b/x-pack/platform/test/serverless/functional/configs/observability/config.group14.ts new file mode 100644 index 0000000000000..e109dd52f3210 --- /dev/null +++ b/x-pack/platform/test/serverless/functional/configs/observability/config.group14.ts @@ -0,0 +1,20 @@ +/* + * 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 baseTestConfig = await readConfigFile(require.resolve('../../config.oblt.base.ts')); + + return { + ...baseTestConfig.getAll(), + testFiles: [require.resolve('../../test_suites/visualizations/group8')], + junit: { + reportName: 'Serverless Observability Functional Tests - Common Group 14', + }, + }; +} diff --git a/x-pack/platform/test/serverless/functional/configs/search/config.group13.ts b/x-pack/platform/test/serverless/functional/configs/search/config.group13.ts new file mode 100644 index 0000000000000..7b58ce95989de --- /dev/null +++ b/x-pack/platform/test/serverless/functional/configs/search/config.group13.ts @@ -0,0 +1,20 @@ +/* + * 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 baseTestConfig = await readConfigFile(require.resolve('../../config.search.base.ts')); + + return { + ...baseTestConfig.getAll(), + testFiles: [require.resolve('../../test_suites/visualizations/group7')], + junit: { + reportName: 'Serverless Search Functional Tests - Common Group 13', + }, + }; +} diff --git a/x-pack/platform/test/serverless/functional/configs/search/config.group14.ts b/x-pack/platform/test/serverless/functional/configs/search/config.group14.ts new file mode 100644 index 0000000000000..e22e9b02408d6 --- /dev/null +++ b/x-pack/platform/test/serverless/functional/configs/search/config.group14.ts @@ -0,0 +1,20 @@ +/* + * 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 baseTestConfig = await readConfigFile(require.resolve('../../config.search.base.ts')); + + return { + ...baseTestConfig.getAll(), + testFiles: [require.resolve('../../test_suites/visualizations/group8')], + junit: { + reportName: 'Serverless Search Functional Tests - Common Group 14', + }, + }; +} diff --git a/x-pack/platform/test/serverless/functional/configs/security/config.group13.ts b/x-pack/platform/test/serverless/functional/configs/security/config.group13.ts new file mode 100644 index 0000000000000..ce88de15a4b59 --- /dev/null +++ b/x-pack/platform/test/serverless/functional/configs/security/config.group13.ts @@ -0,0 +1,20 @@ +/* + * 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 baseTestConfig = await readConfigFile(require.resolve('../../config.security.base.ts')); + + return { + ...baseTestConfig.getAll(), + testFiles: [require.resolve('../../test_suites/visualizations/group7')], + junit: { + reportName: 'Serverless Security Functional Tests - Common Group 13', + }, + }; +} diff --git a/x-pack/platform/test/serverless/functional/configs/security/config.group14.ts b/x-pack/platform/test/serverless/functional/configs/security/config.group14.ts new file mode 100644 index 0000000000000..cf1cf0138660b --- /dev/null +++ b/x-pack/platform/test/serverless/functional/configs/security/config.group14.ts @@ -0,0 +1,20 @@ +/* + * 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 baseTestConfig = await readConfigFile(require.resolve('../../config.security.base.ts')); + + return { + ...baseTestConfig.getAll(), + testFiles: [require.resolve('../../test_suites/visualizations/group8')], + junit: { + reportName: 'Serverless Security Functional Tests - Common Group 14', + }, + }; +} diff --git a/x-pack/platform/test/serverless/functional/test_suites/visualizations/group5/tsdb.ts b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group5/tsdb.ts index a61108219a490..4759ce7197b69 100644 --- a/x-pack/platform/test/serverless/functional/test_suites/visualizations/group5/tsdb.ts +++ b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group5/tsdb.ts @@ -7,12 +7,10 @@ import expect from '@kbn/expect'; import { partition } from 'lodash'; -import moment from 'moment'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { type ScenarioIndexes, TEST_DOC_COUNT, - TIME_PICKER_FORMAT, getDataMapping, getDocsGenerator, setupScenarioRunner, @@ -469,138 +467,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); }); - - describe('TSDB downgraded to regular data stream scenarios', () => { - const tsdbStream = 'tsdb_stream_dowgradable'; - // rollover does not allow to change name, it will just change backing index underneath - const tsdbConvertedToStream = tsdbStream; - - before(async () => { - log.info(`Creating "${tsdbStream}" data stream...`); - await dataStreams.createDataStream(tsdbStream, getDataMapping({ mode: 'tsdb' }), 'tsdb'); - - // add some data to the stream - await createDocs(tsdbStream, { isStream: true }, fromTimeForScenarios); - - log.info(`Update settings for "${tsdbStream}" dataView...`); - await kibanaServer.uiSettings.update({ - 'dateFormat:tz': 'UTC', - 'timepicker:timeDefaults': '{ "from": "now-1y", "to": "now" }', - }); - log.info( - `Dowgrade "${tsdbStream}" stream into regular stream "${tsdbConvertedToStream}"...` - ); - - await dataStreams.downgradeStream(tsdbStream, getDataMapping({ mode: 'tsdb' }), 'tsdb'); - log.info(`Add more data to new "${tsdbConvertedToStream}" dataView (no longer TSDB)...`); - // add some more data when upgraded - await createDocs(tsdbConvertedToStream, { isStream: true }, toTimeForScenarios); - }); - - after(async () => { - await dataStreams.deleteDataStream(tsdbConvertedToStream); - }); - - runTestsForEachScenario(tsdbConvertedToStream, 'tsdb', (indexes) => { - it('should keep TSDB restrictions only if a tsdb stream is in the dataView mix', async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes_counter`, - keepOpen: true, - }); - - expect( - testSubjects.exists(`lns-indexPatternDimension-average incompatible`, { - timeout: 500, - }) - ).to.eql(indexes.some(({ mode }) => mode === 'tsdb')); - await lens.closeDimensionEditor(); - }); - - it(`should visualize a date histogram chart for counter field`, async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - // just check the data is shown - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'count', - }); - - await lens.waitForVisualization('xyVisChart'); - const data = await lens.getCurrentChartDebugState('xyVisChart'); - const bars = data?.bars![0].bars; - const columnsToCheck = bars ? bars.length / 2 : 0; - // due to the flaky nature of exact check here, we're going to relax it - // as long as there's data before and after it is ok - log.info('Check count before the downgrade'); - // Before the upgrade the count is N times the indexes - expect(sumFirstNValues(columnsToCheck, bars)).to.be.greaterThan( - indexes.length * TEST_DOC_COUNT - 1 - ); - log.info('Check count after the downgrade'); - // later there are only documents for the upgraded stream - expect(sumFirstNValues(columnsToCheck, [...(bars ?? [])].reverse())).to.be.greaterThan( - TEST_DOC_COUNT - 1 - ); - }); - - it('should visualize data when moving the time window around the downgrade moment', async () => { - // check after the downgrade - await lens.goToTimeRange( - moment - .utc(fromTimeForScenarios, TIME_PICKER_FORMAT) - .subtract(1, 'hour') - .format(TIME_PICKER_FORMAT), - moment - .utc(fromTimeForScenarios, TIME_PICKER_FORMAT) - .add(1, 'hour') - .format(TIME_PICKER_FORMAT) // consider only new documents - ); - - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'count', - }); - - await lens.waitForVisualization('xyVisChart'); - const dataBefore = await lens.getCurrentChartDebugState('xyVisChart'); - const barsBefore = dataBefore?.bars![0].bars; - expect(barsBefore?.some(({ y }) => y)).to.eql(true); - - // check after the downgrade - await lens.goToTimeRange( - moment - .utc(toTimeForScenarios, TIME_PICKER_FORMAT) - .add(1, 'second') - .format(TIME_PICKER_FORMAT), - moment - .utc(toTimeForScenarios, TIME_PICKER_FORMAT) - .add(2, 'hour') - .format(TIME_PICKER_FORMAT) // consider also new documents - ); - - await lens.waitForVisualization('xyVisChart'); - const dataAfter = await lens.getCurrentChartDebugState('xyVisChart'); - const barsAfter = dataAfter?.bars![0].bars; - expect(barsAfter?.some(({ y }) => y)).to.eql(true); - }); - }); - }); }); }); } diff --git a/x-pack/platform/test/serverless/functional/test_suites/visualizations/group6/logsdb.ts b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group6/logsdb.ts index 4a9f11f7ff3d5..89540a0c4fc96 100644 --- a/x-pack/platform/test/serverless/functional/test_suites/visualizations/group6/logsdb.ts +++ b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group6/logsdb.ts @@ -6,14 +6,12 @@ */ import expect from '@kbn/expect'; -import moment from 'moment'; import type { FtrProviderContext } from '../../../ftr_provider_context'; import { type ScenarioIndexes, getDataMapping, getDocsGenerator, setupScenarioRunner, - TIME_PICKER_FORMAT, } from '../tsdb_logsdb_helpers'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -380,212 +378,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); }); - - describe('LogsDB downgraded to regular data stream scenarios', () => { - const logsdbStream = 'logsdb_stream_dowgradable'; - // rollover does not allow to change name, it will just change backing index underneath - const logsdbConvertedToStream = logsdbStream; - - before(async () => { - log.info(`Creating "${logsdbStream}" data stream...`); - await dataStreams.createDataStream( - logsdbStream, - getDataMapping({ mode: 'logsdb' }), - 'logsdb' - ); - - // add some data to the stream - await createDocs(logsdbStream, { isStream: true }, fromTimeForScenarios); - - log.info(`Update settings for "${logsdbStream}" dataView...`); - await kibanaServer.uiSettings.update({ - 'dateFormat:tz': 'UTC', - 'timepicker:timeDefaults': '{ "from": "now-1y", "to": "now" }', - }); - log.info( - `Dowgrade "${logsdbStream}" stream into regular stream "${logsdbConvertedToStream}"...` - ); - - await dataStreams.downgradeStream( - logsdbStream, - getDataMapping({ mode: 'logsdb' }), - 'logsdb' - ); - log.info( - `Add more data to new "${logsdbConvertedToStream}" dataView (no longer LogsDB)...` - ); - // add some more data when upgraded - await createDocs(logsdbConvertedToStream, { isStream: true }, toTimeForScenarios); - }); - - after(async () => { - await dataStreams.deleteDataStream(logsdbConvertedToStream); - }); - - runTestsForEachScenario(logsdbConvertedToStream, 'logsdb', (indexes) => { - it(`should visualize a date histogram chart`, async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - - // check that a basic agg on a field works - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes`, - }); - - await lens.waitForVisualization('xyVisChart'); - const data = await lens.getCurrentChartDebugState('xyVisChart'); - const bars = data?.bars![0].bars; - - log.info('Check counter data before the upgrade'); - // check there's some data before the upgrade - expect(bars?.[0].y).to.be.above(0); - log.info('Check counter data after the upgrade'); - // check there's some data after the upgrade - expect(bars?.[bars.length - 1].y).to.be.above(0); - }); - - it(`should visualize a date histogram chart using a different date field`, async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: 'utc_time', - }); - - // check the counter field works - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes`, - }); - - await lens.waitForVisualization('xyVisChart'); - const data = await lens.getCurrentChartDebugState('xyVisChart'); - const bars = data?.bars![0].bars; - - log.info('Check counter data before the upgrade'); - // check there's some data before the upgrade - expect(bars?.[0].y).to.be.above(0); - log.info('Check counter data after the upgrade'); - // check there's some data after the upgrade - expect(bars?.[bars.length - 1].y).to.be.above(0); - }); - - it('should visualize an annotation layer from a logsDB stream', async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: 'utc_time', - }); - - // check the counter field works - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes`, - }); - await lens.createLayer('annotations'); - - await lens.assertLayerCount(2); - // switch to the annotation tab - await lens.ensureLayerTabIsActive(1); - expect( - await ( - await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') - ).getVisibleText() - ).to.eql('Event'); - await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); - await testSubjects.click('lnsXY_annotation_query'); - await lens.configureQueryAnnotation({ - queryString: 'host.name: *', - timeField: '@timestamp', - textDecoration: { type: 'name' }, - extraFields: ['host.name', 'utc_time'], - }); - await lens.closeDimensionEditor(); - - await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); - await lens.removeLayer(1); - }); - - it('should visualize an annotation layer from a logsDB stream using another time field', async () => { - await lens.configureDimension({ - dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: 'utc_time', - }); - - // check the counter field works - await lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'min', - field: `bytes`, - }); - await lens.createLayer('annotations'); - - await lens.assertLayerCount(2); - // switch to the annotation tab - await lens.ensureLayerTabIsActive(1); - expect( - await ( - await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') - ).getVisibleText() - ).to.eql('Event'); - await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); - await testSubjects.click('lnsXY_annotation_query'); - await lens.configureQueryAnnotation({ - queryString: 'host.name: *', - timeField: 'utc_time', - textDecoration: { type: 'name' }, - extraFields: ['host.name', '@timestamp'], - }); - await lens.closeDimensionEditor(); - - await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); - await lens.removeLayer(1); - }); - - it('should visualize correctly ES|QL queries based on a LogsDB stream', async () => { - await common.navigateToApp('discover'); - await discover.selectTextBaseLang(); - - // Use the lens page object here also for discover: both use the same timePicker object - await lens.goToTimeRange( - fromTimeForScenarios, - moment - .utc(toTimeForScenarios, TIME_PICKER_FORMAT) - .add(2, 'hour') - .format(TIME_PICKER_FORMAT) - ); - - await header.waitUntilLoadingHasFinished(); - await monacoEditor.setCodeEditorValue( - `from ${indexes - .map(({ index }) => index) - .join(', ')} | stats averageB = avg(bytes) by extension` - ); - await testSubjects.click('querySubmitButton'); - await header.waitUntilLoadingHasFinished(); - await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); - - await header.waitUntilLoadingHasFinished(); - - await retry.waitFor('lens flyout', async () => { - const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased'); - return ( - dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'averageB' - ); - }); - - // go back to Lens to not break the wrapping function - await common.navigateToApp('lens'); - }); - }); - }); }); }); }); diff --git a/x-pack/platform/test/serverless/functional/test_suites/visualizations/group7/index.ts b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group7/index.ts new file mode 100644 index 0000000000000..29f5df02a4122 --- /dev/null +++ b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group7/index.ts @@ -0,0 +1,78 @@ +/* + * 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 { EsArchiver } from '@kbn/es-archiver'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => { + const browser = getService('browser'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['timePicker', 'svlCommonPage']); + const config = getService('config'); + let remoteEsArchiver; + + describe('lens serverless - group 1 - subgroup 7', function () { + this.tags(['esGate']); + + const esArchive = 'x-pack/platform/test/fixtures/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; + before(async () => { + log.debug('Starting lens before method'); + await browser.setWindowSize(1280, 1200); + await kibanaServer.savedObjects.cleanStandardList(); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + }); + + after(async () => { + await esArchiver.unload(esArchive); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + loadTestFile(require.resolve('./tsdb_downgrade.ts')); + }); +}; diff --git a/x-pack/platform/test/serverless/functional/test_suites/visualizations/group7/tsdb_downgrade.ts b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group7/tsdb_downgrade.ts new file mode 100644 index 0000000000000..a8de53eede876 --- /dev/null +++ b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group7/tsdb_downgrade.ts @@ -0,0 +1,242 @@ +/* + * 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 moment from 'moment'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { + type ScenarioIndexes, + TEST_DOC_COUNT, + TIME_PICKER_FORMAT, + getDataMapping, + getDocsGenerator, + setupScenarioRunner, + sumFirstNValues, +} from '../tsdb_logsdb_helpers'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { lens, svlCommonPage } = getPageObjects(['common', 'lens', 'svlCommonPage']); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const es = getService('es'); + const log = getService('log'); + const dataStreams = getService('dataStreams'); + const indexPatterns = getService('indexPatterns'); + const esArchiver = getService('esArchiver'); + + const createDocs = getDocsGenerator(log, es, 'tsdb'); + + describe('lens tsdb downgrade', function () { + const tsdbIndex = 'kibana_sample_data_logstsdb'; + const tsdbDataView = tsdbIndex; + const tsdbEsArchive = + 'src/platform/test/functional/fixtures/es_archiver/kibana_sample_data_logs_tsdb'; + const fromTime = 'Apr 16, 2023 @ 00:00:00.000'; + const toTime = 'Jun 16, 2023 @ 00:00:00.000'; + + before(async () => { + await svlCommonPage.loginAsAdmin(); + log.info(`loading ${tsdbIndex} index...`); + await esArchiver.loadIfNeeded(tsdbEsArchive); + log.info(`creating a data view for "${tsdbDataView}"...`); + await indexPatterns.create( + { + title: tsdbDataView, + timeFieldName: '@timestamp', + }, + { override: true } + ); + log.info(`updating settings to use the "${tsdbDataView}" dataView...`); + await kibanaServer.uiSettings.update({ + 'dateFormat:tz': 'UTC', + defaultIndex: '0ae0bc7a-e4ca-405c-ab67-f2b5913f2a51', + 'timepicker:timeDefaults': `{ "from": "${fromTime}", "to": "${toTime}" }`, + }); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.uiSettings.replace({}); + await es.indices.delete({ index: [tsdbIndex] }); + }); + + describe('Scenarios with changing stream type', () => { + const getScenarios = ( + initialIndex: string + ): Array<{ + name: string; + indexes: ScenarioIndexes[]; + }> => [ + { + name: 'Dataview with no additional stream/index', + indexes: [{ index: initialIndex }], + }, + { + name: 'Dataview with an additional regular index', + indexes: [ + { index: initialIndex }, + { index: 'regular_index', create: true, removeTSDBFields: true }, + ], + }, + { + name: 'Dataview with an additional downsampled TSDB stream', + indexes: [ + { index: initialIndex }, + { index: 'tsdb_index_2', create: true, mode: 'tsdb', downsample: true }, + ], + }, + { + name: 'Dataview with additional regular index and a downsampled TSDB stream', + indexes: [ + { index: initialIndex }, + { index: 'regular_index', create: true, removeTSDBFields: true }, + { index: 'tsdb_index_2', create: true, mode: 'tsdb', downsample: true }, + ], + }, + { + name: 'Dataview with an additional TSDB stream', + indexes: [{ index: initialIndex }, { index: 'tsdb_index_2', create: true, mode: 'tsdb' }], + }, + ]; + + const { runTestsForEachScenario, toTimeForScenarios, fromTimeForScenarios } = + setupScenarioRunner(getService, getPageObjects, getScenarios); + + describe('TSDB downgraded to regular data stream scenarios', () => { + const tsdbStream = 'tsdb_stream_dowgradable'; + // rollover does not allow to change name, it will just change backing index underneath + const tsdbConvertedToStream = tsdbStream; + + before(async () => { + log.info(`Creating "${tsdbStream}" data stream...`); + await dataStreams.createDataStream(tsdbStream, getDataMapping({ mode: 'tsdb' }), 'tsdb'); + + // add some data to the stream + await createDocs(tsdbStream, { isStream: true }, fromTimeForScenarios); + + log.info(`Update settings for "${tsdbStream}" dataView...`); + await kibanaServer.uiSettings.update({ + 'dateFormat:tz': 'UTC', + 'timepicker:timeDefaults': '{ "from": "now-1y", "to": "now" }', + }); + log.info( + `Dowgrade "${tsdbStream}" stream into regular stream "${tsdbConvertedToStream}"...` + ); + + await dataStreams.downgradeStream(tsdbStream, getDataMapping({ mode: 'tsdb' }), 'tsdb'); + log.info(`Add more data to new "${tsdbConvertedToStream}" dataView (no longer TSDB)...`); + // add some more data when upgraded + await createDocs(tsdbConvertedToStream, { isStream: true }, toTimeForScenarios); + }); + + after(async () => { + await dataStreams.deleteDataStream(tsdbConvertedToStream); + }); + + runTestsForEachScenario(tsdbConvertedToStream, 'tsdb', (indexes) => { + it('should keep TSDB restrictions only if a tsdb stream is in the dataView mix', async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes_counter`, + keepOpen: true, + }); + + expect( + testSubjects.exists(`lns-indexPatternDimension-average incompatible`, { + timeout: 500, + }) + ).to.eql(indexes.some(({ mode }) => mode === 'tsdb')); + await lens.closeDimensionEditor(); + }); + + it(`should visualize a date histogram chart for counter field`, async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + // just check the data is shown + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'count', + }); + + await lens.waitForVisualization('xyVisChart'); + const data = await lens.getCurrentChartDebugState('xyVisChart'); + const bars = data?.bars![0].bars; + const columnsToCheck = bars ? bars.length / 2 : 0; + // due to the flaky nature of exact check here, we're going to relax it + // as long as there's data before and after it is ok + log.info('Check count before the downgrade'); + // Before the upgrade the count is N times the indexes + expect(sumFirstNValues(columnsToCheck, bars)).to.be.greaterThan( + indexes.length * TEST_DOC_COUNT - 1 + ); + log.info('Check count after the downgrade'); + // later there are only documents for the upgraded stream + expect(sumFirstNValues(columnsToCheck, [...(bars ?? [])].reverse())).to.be.greaterThan( + TEST_DOC_COUNT - 1 + ); + }); + + it('should visualize data when moving the time window around the downgrade moment', async () => { + // check after the downgrade + await lens.goToTimeRange( + moment + .utc(fromTimeForScenarios, TIME_PICKER_FORMAT) + .subtract(1, 'hour') + .format(TIME_PICKER_FORMAT), + moment + .utc(fromTimeForScenarios, TIME_PICKER_FORMAT) + .add(1, 'hour') + .format(TIME_PICKER_FORMAT) // consider only new documents + ); + + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'count', + }); + + await lens.waitForVisualization('xyVisChart'); + const dataBefore = await lens.getCurrentChartDebugState('xyVisChart'); + const barsBefore = dataBefore?.bars![0].bars; + expect(barsBefore?.some(({ y }) => y)).to.eql(true); + + // check after the downgrade + await lens.goToTimeRange( + moment + .utc(toTimeForScenarios, TIME_PICKER_FORMAT) + .add(1, 'second') + .format(TIME_PICKER_FORMAT), + moment + .utc(toTimeForScenarios, TIME_PICKER_FORMAT) + .add(2, 'hour') + .format(TIME_PICKER_FORMAT) // consider also new documents + ); + + await lens.waitForVisualization('xyVisChart'); + const dataAfter = await lens.getCurrentChartDebugState('xyVisChart'); + const barsAfter = dataAfter?.bars![0].bars; + expect(barsAfter?.some(({ y }) => y)).to.eql(true); + }); + }); + }); + }); + }); +} diff --git a/x-pack/platform/test/serverless/functional/test_suites/visualizations/group8/index.ts b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group8/index.ts new file mode 100644 index 0000000000000..e790dc34abb2c --- /dev/null +++ b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group8/index.ts @@ -0,0 +1,78 @@ +/* + * 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 { EsArchiver } from '@kbn/es-archiver'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getService, loadTestFile, getPageObjects }: FtrProviderContext) => { + const browser = getService('browser'); + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['timePicker', 'svlCommonPage']); + const config = getService('config'); + let remoteEsArchiver; + + describe('lens serverless - group 1 - subgroup 8', function () { + this.tags(['esGate']); + + const esArchive = 'x-pack/platform/test/fixtures/es_archives/logstash_functional'; + const localIndexPatternString = 'logstash-*'; + const remoteIndexPatternString = 'ftr-remote:logstash-*'; + const localFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/default', + }; + + const remoteFixtures = { + lensBasic: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/lens_basic.json', + lensDefault: 'x-pack/platform/test/functional/fixtures/kbn_archives/lens/ccs/default', + }; + let esNode: EsArchiver; + let fixtureDirs: { + lensBasic: string; + lensDefault: string; + }; + let indexPatternString: string; + before(async () => { + log.debug('Starting lens before method'); + await browser.setWindowSize(1280, 1200); + await kibanaServer.savedObjects.cleanStandardList(); + try { + config.get('esTestCluster.ccs'); + remoteEsArchiver = getService('remoteEsArchiver' as 'esArchiver'); + esNode = remoteEsArchiver; + fixtureDirs = remoteFixtures; + indexPatternString = remoteIndexPatternString; + } catch (error) { + esNode = esArchiver; + fixtureDirs = localFixtures; + indexPatternString = localIndexPatternString; + } + + await esNode.load(esArchive); + await kibanaServer.uiSettings.update({ + defaultIndex: indexPatternString, + 'dateFormat:tz': 'UTC', + }); + await kibanaServer.importExport.load(fixtureDirs.lensBasic); + await kibanaServer.importExport.load(fixtureDirs.lensDefault); + // changing the timepicker default here saves us from having to set it in Discover (~8s) + await PageObjects.timePicker.setDefaultAbsoluteRangeViaUiSettings(); + }); + + after(async () => { + await esArchiver.unload(esArchive); + await PageObjects.timePicker.resetDefaultAbsoluteRangeViaUiSettings(); + await kibanaServer.importExport.unload(fixtureDirs.lensBasic); + await kibanaServer.importExport.unload(fixtureDirs.lensDefault); + await kibanaServer.savedObjects.cleanStandardList(); + }); + + loadTestFile(require.resolve('./logsdb_downgrade.ts')); + }); +}; diff --git a/x-pack/platform/test/serverless/functional/test_suites/visualizations/group8/logsdb_downgrade.ts b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group8/logsdb_downgrade.ts new file mode 100644 index 0000000000000..52c3d8db18d11 --- /dev/null +++ b/x-pack/platform/test/serverless/functional/test_suites/visualizations/group8/logsdb_downgrade.ts @@ -0,0 +1,333 @@ +/* + * 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 moment from 'moment'; +import type { FtrProviderContext } from '../../../ftr_provider_context'; +import { + type ScenarioIndexes, + getDataMapping, + getDocsGenerator, + setupScenarioRunner, + TIME_PICKER_FORMAT, +} from '../tsdb_logsdb_helpers'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const { common, lens, discover, header, timePicker, svlCommonPage } = getPageObjects([ + 'common', + 'lens', + 'discover', + 'header', + 'timePicker', + 'svlCommonPage', + ]); + const testSubjects = getService('testSubjects'); + const kibanaServer = getService('kibanaServer'); + const es = getService('es'); + const log = getService('log'); + const dataStreams = getService('dataStreams'); + const indexPatterns = getService('indexPatterns'); + const esArchiver = getService('esArchiver'); + const monacoEditor = getService('monacoEditor'); + const retry = getService('retry'); + + const createDocs = getDocsGenerator(log, es, 'logsdb'); + + describe('lens logsdb downgrade', function () { + // see details: https://github.com/elastic/kibana/issues/195089 + this.tags(['failsOnMKI']); + const logsdbIndex = 'kibana_sample_data_logslogsdb'; + const logsdbDataView = logsdbIndex; + const logsdbEsArchive = + 'src/platform/test/functional/fixtures/es_archiver/kibana_sample_data_logs_logsdb'; + const fromTime = 'Apr 16, 2023 @ 00:00:00.000'; + const toTime = 'Jun 16, 2023 @ 00:00:00.000'; + + before(async () => { + await svlCommonPage.loginAsAdmin(); + log.info(`loading ${logsdbIndex} index...`); + await esArchiver.loadIfNeeded(logsdbEsArchive); + log.info(`creating a data view for "${logsdbDataView}"...`); + await indexPatterns.create( + { + title: logsdbDataView, + timeFieldName: '@timestamp', + }, + { override: true } + ); + log.info(`updating settings to use the "${logsdbDataView}" dataView...`); + await kibanaServer.uiSettings.update({ + 'dateFormat:tz': 'UTC', + defaultIndex: '0ae0bc7a-e4ca-405c-ab67-f2b5913f2a51', + 'timepicker:timeDefaults': `{ "from": "${fromTime}", "to": "${toTime}" }`, + }); + }); + + after(async () => { + await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.uiSettings.replace({}); + await timePicker.setDefaultAbsoluteRangeViaUiSettings(); + await es.indices.delete({ index: [logsdbIndex] }); + }); + + describe('Scenarios with changing stream type', () => { + const getScenarios = ( + initialIndex: string + ): Array<{ + name: string; + indexes: ScenarioIndexes[]; + }> => [ + { + name: 'LogsDB stream with no additional stream/index', + indexes: [{ index: initialIndex }], + }, + { + name: 'LogsDB stream with no additional stream/index and no host.name field', + indexes: [ + { + index: `${initialIndex}_no_host`, + removeLogsDBFields: true, + create: true, + mode: 'logsdb', + }, + ], + }, + { + name: 'LogsDB stream with an additional regular index', + indexes: [{ index: initialIndex }, { index: 'regular_index', create: true }], + }, + { + name: 'LogsDB stream with an additional LogsDB stream', + indexes: [ + { index: initialIndex }, + { index: 'logsdb_index_2', create: true, mode: 'logsdb' }, + ], + }, + { + name: 'LogsDB stream with an additional TSDB stream', + indexes: [{ index: initialIndex }, { index: 'tsdb_index', create: true, mode: 'tsdb' }], + }, + { + name: 'LogsDB stream with an additional TSDB stream downsampled', + indexes: [ + { index: initialIndex }, + { index: 'tsdb_index_downsampled', create: true, mode: 'tsdb', downsample: true }, + ], + }, + ]; + + const { runTestsForEachScenario, toTimeForScenarios, fromTimeForScenarios } = + setupScenarioRunner(getService, getPageObjects, getScenarios); + + describe('LogsDB downgraded to regular data stream scenarios', () => { + const logsdbStream = 'logsdb_stream_dowgradable'; + // rollover does not allow to change name, it will just change backing index underneath + const logsdbConvertedToStream = logsdbStream; + + before(async () => { + log.info(`Creating "${logsdbStream}" data stream...`); + await dataStreams.createDataStream( + logsdbStream, + getDataMapping({ mode: 'logsdb' }), + 'logsdb' + ); + + // add some data to the stream + await createDocs(logsdbStream, { isStream: true }, fromTimeForScenarios); + + log.info(`Update settings for "${logsdbStream}" dataView...`); + await kibanaServer.uiSettings.update({ + 'dateFormat:tz': 'UTC', + 'timepicker:timeDefaults': '{ "from": "now-1y", "to": "now" }', + }); + log.info( + `Dowgrade "${logsdbStream}" stream into regular stream "${logsdbConvertedToStream}"...` + ); + + await dataStreams.downgradeStream( + logsdbStream, + getDataMapping({ mode: 'logsdb' }), + 'logsdb' + ); + log.info( + `Add more data to new "${logsdbConvertedToStream}" dataView (no longer LogsDB)...` + ); + // add some more data when upgraded + await createDocs(logsdbConvertedToStream, { isStream: true }, toTimeForScenarios); + }); + + after(async () => { + await dataStreams.deleteDataStream(logsdbConvertedToStream); + }); + + runTestsForEachScenario(logsdbConvertedToStream, 'logsdb', (indexes) => { + it(`should visualize a date histogram chart`, async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + // check that a basic agg on a field works + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes`, + }); + + await lens.waitForVisualization('xyVisChart'); + const data = await lens.getCurrentChartDebugState('xyVisChart'); + const bars = data?.bars![0].bars; + + log.info('Check counter data before the upgrade'); + // check there's some data before the upgrade + expect(bars?.[0].y).to.be.above(0); + log.info('Check counter data after the upgrade'); + // check there's some data after the upgrade + expect(bars?.[bars.length - 1].y).to.be.above(0); + }); + + it(`should visualize a date histogram chart using a different date field`, async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: 'utc_time', + }); + + // check the counter field works + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes`, + }); + + await lens.waitForVisualization('xyVisChart'); + const data = await lens.getCurrentChartDebugState('xyVisChart'); + const bars = data?.bars![0].bars; + + log.info('Check counter data before the upgrade'); + // check there's some data before the upgrade + expect(bars?.[0].y).to.be.above(0); + log.info('Check counter data after the upgrade'); + // check there's some data after the upgrade + expect(bars?.[bars.length - 1].y).to.be.above(0); + }); + + it('should visualize an annotation layer from a logsDB stream', async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: 'utc_time', + }); + + // check the counter field works + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes`, + }); + await lens.createLayer('annotations'); + + await lens.assertLayerCount(2); + // switch to the annotation tab + await lens.ensureLayerTabIsActive(1); + expect( + await ( + await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') + ).getVisibleText() + ).to.eql('Event'); + await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); + await testSubjects.click('lnsXY_annotation_query'); + await lens.configureQueryAnnotation({ + queryString: 'host.name: *', + timeField: '@timestamp', + textDecoration: { type: 'name' }, + extraFields: ['host.name', 'utc_time'], + }); + await lens.closeDimensionEditor(); + + await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); + await lens.removeLayer(1); + }); + + it('should visualize an annotation layer from a logsDB stream using another time field', async () => { + await lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: 'utc_time', + }); + + // check the counter field works + await lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'min', + field: `bytes`, + }); + await lens.createLayer('annotations'); + + await lens.assertLayerCount(2); + // switch to the annotation tab + await lens.ensureLayerTabIsActive(1); + expect( + await ( + await testSubjects.find('lnsXY_xAnnotationsPanel > lns-dimensionTrigger') + ).getVisibleText() + ).to.eql('Event'); + await testSubjects.click('lnsXY_xAnnotationsPanel > lns-dimensionTrigger'); + await testSubjects.click('lnsXY_annotation_query'); + await lens.configureQueryAnnotation({ + queryString: 'host.name: *', + timeField: 'utc_time', + textDecoration: { type: 'name' }, + extraFields: ['host.name', '@timestamp'], + }); + await lens.closeDimensionEditor(); + + await testSubjects.existOrFail('xyVisGroupedAnnotationIcon'); + await lens.removeLayer(1); + }); + + it('should visualize correctly ES|QL queries based on a LogsDB stream', async () => { + await common.navigateToApp('discover'); + await discover.selectTextBaseLang(); + + // Use the lens page object here also for discover: both use the same timePicker object + await lens.goToTimeRange( + fromTimeForScenarios, + moment + .utc(toTimeForScenarios, TIME_PICKER_FORMAT) + .add(2, 'hour') + .format(TIME_PICKER_FORMAT) + ); + + await header.waitUntilLoadingHasFinished(); + await monacoEditor.setCodeEditorValue( + `from ${indexes + .map(({ index }) => index) + .join(', ')} | stats averageB = avg(bytes) by extension` + ); + await testSubjects.click('querySubmitButton'); + await header.waitUntilLoadingHasFinished(); + await testSubjects.click('unifiedHistogramEditFlyoutVisualization'); + + await header.waitUntilLoadingHasFinished(); + + await retry.waitFor('lens flyout', async () => { + const dimensions = await testSubjects.findAll('lns-dimensionTrigger-textBased'); + return ( + dimensions.length === 2 && (await dimensions[1].getVisibleText()) === 'averageB' + ); + }); + + // go back to Lens to not break the wrapping function + await common.navigateToApp('lens'); + }); + }); + }); + }); + }); +}