diff --git a/docs/api/alerting/create_rule.asciidoc b/docs/api/alerting/create_rule.asciidoc index 59b17c5c3b5e1..79ae7b0c39d6c 100644 --- a/docs/api/alerting/create_rule.asciidoc +++ b/docs/api/alerting/create_rule.asciidoc @@ -6,8 +6,6 @@ Create {kib} rules. -WARNING: This API supports <> only. - [[create-rule-api-request]] ==== Request @@ -15,6 +13,17 @@ WARNING: This API supports <> only. `POST :/s//api/alerting/rule/` +==== {api-description-title} + +[WARNING] +==== +* This API supports only +<>. +* When you create a rule, it identifies which roles you have at that point in time. +Thereafter, when the rule performs queries, it uses those security privileges. +If a user with different privileges updates the rule, its behavior might change. +==== + [[create-rule-api-path-params]] ==== Path parameters diff --git a/docs/api/alerting/update_rule.asciidoc b/docs/api/alerting/update_rule.asciidoc index ec82e60a8e879..5fb8f17d6ebb5 100644 --- a/docs/api/alerting/update_rule.asciidoc +++ b/docs/api/alerting/update_rule.asciidoc @@ -6,8 +6,6 @@ Update the attributes for an existing rule. -WARNING: This API supports <> only. - [[update-rule-api-request]] ==== Request @@ -15,6 +13,18 @@ WARNING: This API supports <> only. `PUT :/s//api/alerting/rule/` +==== {api-description-title} + +[WARNING] +==== +* This API supports only +<>. +* When you update a rule, it identifies which roles you have at that point in time. +Thereafter, when the rule performs queries, it uses those security privileges. +If you have different privileges than the user that created or most recently +updated the rule, you might change its behavior. +==== + [[update-rule-api-path-params]] ==== Path parameters diff --git a/docs/setup/upgrade/resolving-migration-failures.asciidoc b/docs/setup/upgrade/resolving-migration-failures.asciidoc index f90a9f541f3eb..7cbc972b102ab 100644 --- a/docs/setup/upgrade/resolving-migration-failures.asciidoc +++ b/docs/setup/upgrade/resolving-migration-failures.asciidoc @@ -171,7 +171,7 @@ Upgrade migrations fail because routing allocation is disabled or restricted (`c [source,sh] -------------------------------------------- -Unable to complete saved object migrations for the [.kibana] index: [unsupported_cluster_routing_allocation] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue. To proceed, please remove the cluster routing allocation settings with PUT /_cluster/settings {"transient": {"cluster.routing.allocation.enable": null}, "persistent": {"cluster.routing.allocation.enable": null}} +Unable to complete saved object migrations for the [.kibana] index: [incompatible_cluster_routing_allocation] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue. To proceed, please remove the cluster routing allocation settings with PUT /_cluster/settings {"transient": {"cluster.routing.allocation.enable": null}, "persistent": {"cluster.routing.allocation.enable": null}} -------------------------------------------- To get around the issue, remove the transient and persisted routing allocation settings: diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 0e7d4939cbc29..7fbe272e01bb0 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -127,5 +127,5 @@ pageLoadAssetSize: eventAnnotation: 19334 screenshotting: 22870 synthetics: 40958 - expressionXY: 29000 + expressionXY: 30000 kibanaUsageCollection: 16463 diff --git a/src/core/server/saved_objects/migrations/README.md b/src/core/server/saved_objects/migrations/README.md index 7dd5dacffcee6..03bbb0bc731c4 100644 --- a/src/core/server/saved_objects/migrations/README.md +++ b/src/core/server/saved_objects/migrations/README.md @@ -149,8 +149,12 @@ index. ### New control state 1. Two conditions have to be met before migrations begin: - 1. If replica allocation is set as a persistent or transient setting to "perimaries", "new_primaries" or "none" fail the migration. Without replica allocation enabled or not set to 'all', the migration will timeout when waiting for index yellow status before bulk indexing. The check only considers persistent and transient settings and does not take static configuration in `elasticsearch.yml` into account. If `cluster.routing.allocation.enable` is configured in `elaticsearch.yml` and not set to the default of 'all', the migration will timeout. Static settings can only be returned from the `nodes/info` API. - → `FATAL` + 1. The Elasticsearch shard allocation cluster setting `cluster.routing.allocation.enable` needs to be unset or set to 'all'. When set to 'primaries', 'new_primaries' or 'none', the migration will timeout when waiting for index yellow status before bulk indexing because the replica cannot be allocated. + + As per the Elasticsearch docs https://www.elastic.co/guide/en/elasticsearch/reference/8.2/restart-cluster.html#restart-cluster-rolling when Cloud performs a rolling restart such as during an upgrade, it will temporarily disable shard allocation. Kibana therefore keeps retrying the INIT step to wait for shard allocation to be enabled again. + + The check only considers persistent and transient settings and does not take static configuration in `elasticsearch.yml` into account since there are no known use cases for doing so. If `cluster.routing.allocation.enable` is configured in `elaticsearch.yml` and not set to the default of 'all', the migration will timeout. Static settings can only be returned from the `nodes/info` API. + → `INIT` 2. If `.kibana` is pointing to an index that belongs to a later version of Kibana .e.g. a 7.11.0 instance found the `.kibana` alias pointing to diff --git a/src/core/server/saved_objects/migrations/actions/index.ts b/src/core/server/saved_objects/migrations/actions/index.ts index 4db260d4c139b..74d8c57ebf171 100644 --- a/src/core/server/saved_objects/migrations/actions/index.ts +++ b/src/core/server/saved_objects/migrations/actions/index.ts @@ -20,7 +20,7 @@ export { export type { RetryableEsClientError }; // actions/* imports -export type { InitActionParams, UnsupportedClusterRoutingAllocation } from './initialize_action'; +export type { InitActionParams, IncompatibleClusterRoutingAllocation } from './initialize_action'; export { initAction } from './initialize_action'; export type { FetchIndexResponse, FetchIndicesParams } from './fetch_indices'; @@ -87,7 +87,7 @@ export type { export { updateAndPickupMappings } from './update_and_pickup_mappings'; import type { UnknownDocsFound } from './check_for_unknown_docs'; -import type { UnsupportedClusterRoutingAllocation } from './initialize_action'; +import type { IncompatibleClusterRoutingAllocation } from './initialize_action'; export type { CheckForUnknownDocsParams, @@ -151,7 +151,7 @@ export interface ActionErrorTypeMap { documents_transform_failed: DocumentsTransformFailed; request_entity_too_large_exception: RequestEntityTooLargeException; unknown_docs_found: UnknownDocsFound; - unsupported_cluster_routing_allocation: UnsupportedClusterRoutingAllocation; + incompatible_cluster_routing_allocation: IncompatibleClusterRoutingAllocation; index_not_yellow_timeout: IndexNotYellowTimeout; } diff --git a/src/core/server/saved_objects/migrations/actions/initialize_action.ts b/src/core/server/saved_objects/migrations/actions/initialize_action.ts index e7f011cb4c5f2..b797d81a46ec3 100644 --- a/src/core/server/saved_objects/migrations/actions/initialize_action.ts +++ b/src/core/server/saved_objects/migrations/actions/initialize_action.ts @@ -27,9 +27,8 @@ export interface InitActionParams { indices: string[]; } -export interface UnsupportedClusterRoutingAllocation { - type: 'unsupported_cluster_routing_allocation'; - message: string; +export interface IncompatibleClusterRoutingAllocation { + type: 'incompatible_cluster_routing_allocation'; } export const checkClusterRoutingAllocationEnabledTask = @@ -37,7 +36,7 @@ export const checkClusterRoutingAllocationEnabledTask = client, }: { client: ElasticsearchClient; - }): TaskEither.TaskEither => + }): TaskEither.TaskEither => () => { return client.cluster .getSettings({ @@ -54,9 +53,7 @@ export const checkClusterRoutingAllocationEnabledTask = if (!clusterRoutingAllocationEnabledIsAll) { return Either.left({ - type: 'unsupported_cluster_routing_allocation' as const, - message: - '[unsupported_cluster_routing_allocation] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue.', + type: 'incompatible_cluster_routing_allocation' as const, }); } else { return Either.right({}); @@ -69,7 +66,7 @@ export const initAction = ({ client, indices, }: InitActionParams): TaskEither.TaskEither< - RetryableEsClientError | UnsupportedClusterRoutingAllocation, + RetryableEsClientError | IncompatibleClusterRoutingAllocation, FetchIndexResponse > => { return pipe( diff --git a/src/core/server/saved_objects/migrations/actions/integration_tests/actions.test.ts b/src/core/server/saved_objects/migrations/actions/integration_tests/actions.test.ts index cddd2f323f1fc..7ac6911ec7e9e 100644 --- a/src/core/server/saved_objects/migrations/actions/integration_tests/actions.test.ts +++ b/src/core/server/saved_objects/migrations/actions/integration_tests/actions.test.ts @@ -167,8 +167,7 @@ describe('migration actions', () => { Object { "_tag": "Left", "left": Object { - "message": "[unsupported_cluster_routing_allocation] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue.", - "type": "unsupported_cluster_routing_allocation", + "type": "incompatible_cluster_routing_allocation", }, } `); @@ -188,8 +187,7 @@ describe('migration actions', () => { Object { "_tag": "Left", "left": Object { - "message": "[unsupported_cluster_routing_allocation] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue.", - "type": "unsupported_cluster_routing_allocation", + "type": "incompatible_cluster_routing_allocation", }, } `); @@ -209,8 +207,7 @@ describe('migration actions', () => { Object { "_tag": "Left", "left": Object { - "message": "[unsupported_cluster_routing_allocation] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue.", - "type": "unsupported_cluster_routing_allocation", + "type": "incompatible_cluster_routing_allocation", }, } `); diff --git a/src/core/server/saved_objects/migrations/integration_tests/archives/8.0.0_migrated_with_outdated_docs.zip b/src/core/server/saved_objects/migrations/integration_tests/archives/8.0.0_migrated_with_outdated_docs.zip index ca936c6448b43..1fa9f061e15ff 100644 Binary files a/src/core/server/saved_objects/migrations/integration_tests/archives/8.0.0_migrated_with_outdated_docs.zip and b/src/core/server/saved_objects/migrations/integration_tests/archives/8.0.0_migrated_with_outdated_docs.zip differ diff --git a/src/core/server/saved_objects/migrations/integration_tests/cluster_routing_allocation_disabled.test.ts b/src/core/server/saved_objects/migrations/integration_tests/incompatible_cluster_routing_allocation.test.ts similarity index 56% rename from src/core/server/saved_objects/migrations/integration_tests/cluster_routing_allocation_disabled.test.ts rename to src/core/server/saved_objects/migrations/integration_tests/incompatible_cluster_routing_allocation.test.ts index 525b9b3585c3f..57efba56936bd 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/cluster_routing_allocation_disabled.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/incompatible_cluster_routing_allocation.test.ts @@ -15,7 +15,7 @@ import { ElasticsearchClient } from '../../../elasticsearch'; import { LogRecord } from '@kbn/logging'; import { retryAsync } from '../test_helpers/retry_async'; -const logFilePath = Path.join(__dirname, 'unsupported_cluster_routing_allocation.log'); +const logFilePath = Path.join(__dirname, 'incompatible_cluster_routing_allocation.log'); async function removeLogFile() { // ignore errors if it doesn't exist @@ -27,7 +27,11 @@ const { startES } = kbnTestServer.createTestServers({ settings: { es: { license: 'basic', - dataArchive: Path.join(__dirname, 'archives', '7.7.2_xpack_100k_obj.zip'), + dataArchive: Path.join( + __dirname, + 'archives', + '8.0.0_v1_migrations_sample_data_saved_objects.zip' + ), }, }, }); @@ -77,14 +81,14 @@ let esServer: kbnTestServer.TestElasticsearchUtils; async function updateRoutingAllocations( esClient: ElasticsearchClient, settingType: string = 'persistent', - value: string = 'none' + value: string | null ) { return await esClient.cluster.putSettings({ [settingType]: { cluster: { routing: { allocation: { enable: value } } } }, }); } -describe('unsupported_cluster_routing_allocation', () => { +describe('incompatible_cluster_routing_allocation', () => { let client: ElasticsearchClient; let root: Root; @@ -97,7 +101,7 @@ describe('unsupported_cluster_routing_allocation', () => { await esServer.stop(); }); - it('fails with a descriptive message when persistent replica allocation is not enabled', async () => { + it('retries the INIT action with a descriptive message when cluster settings are incompatible', async () => { const initialSettings = await client.cluster.getSettings({ flat_settings: true }); expect(getClusterRoutingAllocations(initialSettings)).toBe(true); @@ -108,15 +112,14 @@ describe('unsupported_cluster_routing_allocation', () => { expect(getClusterRoutingAllocations(updatedSettings)).toBe(false); - // now try to start Kibana + // Start Kibana root = createKbnRoot(); await root.preboot(); await root.setup(); - await expect(root.start()).rejects.toThrowError( - /Unable to complete saved object migrations for the \[\.kibana.*\] index: \[unsupported_cluster_routing_allocation\] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue\. To proceed, please remove the cluster routing allocation settings with PUT \/_cluster\/settings {\"transient\": {\"cluster\.routing\.allocation\.enable\": null}, \"persistent\": {\"cluster\.routing\.allocation\.enable\": null}}\. Refer to https:\/\/www.elastic.co\/guide\/en\/kibana\/master\/resolve-migrations-failures.html#routing-allocation-disabled for more information on how to resolve the issue\./ - ); + root.start(); + // Wait for the INIT -> INIT action retry await retryAsync( async () => { const logFileContent = await fs.readFile(logFilePath, 'utf-8'); @@ -124,32 +127,38 @@ describe('unsupported_cluster_routing_allocation', () => { .split('\n') .filter(Boolean) .map((str) => JSON5.parse(str)) as LogRecord[]; + + // Wait for logs of the second failed attempt to be sure we're correctly incrementing retries expect( records.find((rec) => - /^Unable to complete saved object migrations for the \[\.kibana.*\] index: \[unsupported_cluster_routing_allocation\] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue\./.test( - rec.message + rec.message.includes( + `Action failed with '[incompatible_cluster_routing_allocation] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to https://www.elastic.co/guide/en/kibana/master/resolve-migrations-failures.html#routing-allocation-disabled for more information on how to resolve the issue.'. Retrying attempt 2 in 4 seconds.` ) ) ).toBeDefined(); }, - { retryAttempts: 10, retryDelayMs: 200 } + { retryAttempts: 20, retryDelayMs: 500 } ); - }); - it('fails with a descriptive message when persistent replica allocation is set to "primaries"', async () => { - await updateRoutingAllocations(client, 'persistent', 'primaries'); + // Reset the cluster routing allocation settings + await updateRoutingAllocations(client, 'persistent', null); - const updatedSettings = await client.cluster.getSettings({ flat_settings: true }); - - expect(getClusterRoutingAllocations(updatedSettings)).toBe(false); - - // now try to start Kibana - root = createKbnRoot(); - await root.preboot(); - await root.setup(); + // Wait for migrations to succeed + await retryAsync( + async () => { + const logFileContent = await fs.readFile(logFilePath, 'utf-8'); + const records = logFileContent + .split('\n') + .filter(Boolean) + .map((str) => JSON5.parse(str)) as LogRecord[]; - await expect(root.start()).rejects.toThrowError( - /Unable to complete saved object migrations for the \[\.kibana.*\] index: \[unsupported_cluster_routing_allocation\] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue\. To proceed, please remove the cluster routing allocation settings with PUT \/_cluster\/settings {\"transient\": {\"cluster\.routing\.allocation\.enable\": null}, \"persistent\": {\"cluster\.routing\.allocation\.enable\": null}}\. Refer to https:\/\/www.elastic.co\/guide\/en\/kibana\/master\/resolve-migrations-failures.html#routing-allocation-disabled for more information on how to resolve the issue\./ + expect( + records.find((rec) => rec.message.includes('MARK_VERSION_INDEX_READY -> DONE')) + ).toBeDefined(); + }, + { retryAttempts: 100, retryDelayMs: 500 } ); + + await root.shutdown(); }); }); diff --git a/src/core/server/saved_objects/migrations/integration_tests/outdated_docs.test.ts b/src/core/server/saved_objects/migrations/integration_tests/outdated_docs.test.ts index c62a764aea653..bc1f41b542380 100644 --- a/src/core/server/saved_objects/migrations/integration_tests/outdated_docs.test.ts +++ b/src/core/server/saved_objects/migrations/integration_tests/outdated_docs.test.ts @@ -41,7 +41,7 @@ describe('migration v2', () => { await new Promise((resolve) => setTimeout(resolve, 10000)); }); - it.skip('migrates the documents to the highest version', async () => { + it('migrates the documents to the highest version', async () => { const migratedIndex = `.kibana_${pkg.version}_001`; const { startES } = kbnTestServer.createTestServers({ adjustTimeout: (t: number) => jest.setTimeout(t), @@ -86,7 +86,7 @@ describe('migration v2', () => { expect(migratedDocs.length).toBe(1); const [doc] = migratedDocs; expect(doc._source.migrationVersion.foo).toBe('7.14.0'); - expect(doc._source.coreMigrationVersion).toBe('8.0.0'); + expect(doc._source.coreMigrationVersion).toBe(pkg.version); }); }); diff --git a/src/core/server/saved_objects/migrations/model/extract_errors.test.ts b/src/core/server/saved_objects/migrations/model/extract_errors.test.ts index e434a5001a6ae..ea6b312c2053d 100644 --- a/src/core/server/saved_objects/migrations/model/extract_errors.test.ts +++ b/src/core/server/saved_objects/migrations/model/extract_errors.test.ts @@ -8,7 +8,6 @@ import { extractUnknownDocFailureReason, - fatalReasonClusterRoutingAllocationUnsupported, fatalReasonDocumentExceedsMaxBatchSizeBytes, } from './extract_errors'; @@ -55,18 +54,3 @@ describe('fatalReasonDocumentExceedsMaxBatchSizeBytes', () => { ); }); }); - -describe('fatalReasonClusterRoutingAllocationUnsupported', () => { - it('generates the correct error message', () => { - const errorMessages = fatalReasonClusterRoutingAllocationUnsupported({ - errorMessage: '[some-error] message', - docSectionLink: 'linkToDocsSection', - }); - expect(errorMessages.fatalReason).toMatchInlineSnapshot( - `"[some-error] message To proceed, please remove the cluster routing allocation settings with PUT /_cluster/settings {\\"transient\\": {\\"cluster.routing.allocation.enable\\": null}, \\"persistent\\": {\\"cluster.routing.allocation.enable\\": null}}. Refer to linkToDocsSection for more information on how to resolve the issue."` - ); - expect(errorMessages.logsErrorMessage).toMatchInlineSnapshot( - `"[some-error] message Ensure that the persistent and transient Elasticsearch configuration option 'cluster.routing.allocation.enable' is not set or set it to a value of 'all'. Refer to linkToDocsSection for more information on how to resolve the issue."` - ); - }); -}); diff --git a/src/core/server/saved_objects/migrations/model/extract_errors.ts b/src/core/server/saved_objects/migrations/model/extract_errors.ts index f41009ab2127c..c529e1b1da269 100644 --- a/src/core/server/saved_objects/migrations/model/extract_errors.ts +++ b/src/core/server/saved_objects/migrations/model/extract_errors.ts @@ -65,18 +65,3 @@ export const fatalReasonDocumentExceedsMaxBatchSizeBytes = ({ maxBatchSizeBytes: number; }) => `The document with _id "${_id}" is ${docSizeBytes} bytes which exceeds the configured maximum batch size of ${maxBatchSizeBytes} bytes. To proceed, please increase the 'migrations.maxBatchSizeBytes' Kibana configuration option and ensure that the Elasticsearch 'http.max_content_length' configuration option is set to an equal or larger value.`; - -/** - * Constructs migration failure message and logs message strings when an unsupported cluster routing allocation is configured. - * The full errorMessage is "[unsupported_cluster_routing_allocation] The elasticsearch cluster has cluster routing allocation incorrectly set for migrations to continue." - */ -export const fatalReasonClusterRoutingAllocationUnsupported = ({ - errorMessage, - docSectionLink, -}: { - errorMessage: string; - docSectionLink: string; -}) => ({ - fatalReason: `${errorMessage} To proceed, please remove the cluster routing allocation settings with PUT /_cluster/settings {"transient": {"cluster.routing.allocation.enable": null}, "persistent": {"cluster.routing.allocation.enable": null}}. Refer to ${docSectionLink} for more information on how to resolve the issue.`, - logsErrorMessage: `${errorMessage} Ensure that the persistent and transient Elasticsearch configuration option 'cluster.routing.allocation.enable' is not set or set it to a value of 'all'. Refer to ${docSectionLink} for more information on how to resolve the issue.`, -}); diff --git a/src/core/server/saved_objects/migrations/model/model.test.ts b/src/core/server/saved_objects/migrations/model/model.test.ts index e44995ac8d30e..e46024fc729d7 100644 --- a/src/core/server/saved_objects/migrations/model/model.test.ts +++ b/src/core/server/saved_objects/migrations/model/model.test.ts @@ -282,17 +282,21 @@ describe('migrations v2 model', () => { expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); - test('INIT -> FATAL when cluster routing allocation is not enabled', () => { + test('INIT -> INIT when cluster routing allocation is incompatible', () => { const res: ResponseType<'INIT'> = Either.left({ - type: 'unsupported_cluster_routing_allocation', - message: '[unsupported_cluster_routing_allocation]', + type: 'incompatible_cluster_routing_allocation', }); const newState = model(initState, res) as FatalState; - expect(newState.controlState).toEqual('FATAL'); - expect(newState.reason).toMatchInlineSnapshot( - `"[unsupported_cluster_routing_allocation] To proceed, please remove the cluster routing allocation settings with PUT /_cluster/settings {\\"transient\\": {\\"cluster.routing.allocation.enable\\": null}, \\"persistent\\": {\\"cluster.routing.allocation.enable\\": null}}. Refer to routingAllocationDisabled for more information on how to resolve the issue."` - ); + expect(newState.controlState).toEqual('INIT'); + expect(newState.retryCount).toEqual(1); + expect(newState.retryDelay).toEqual(2000); + expect(newState.logs[0]).toMatchInlineSnapshot(` + Object { + "level": "error", + "message": "Action failed with '[incompatible_cluster_routing_allocation] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to routingAllocationDisabled for more information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", + } + `); }); test("INIT -> FATAL when .kibana points to newer version's index", () => { const res: ResponseType<'INIT'> = Either.right({ @@ -575,6 +579,12 @@ describe('migrations v2 model', () => { expect(newState.controlState).toEqual('LEGACY_CREATE_REINDEX_TARGET'); expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); + expect(newState.logs[0]).toMatchInlineSnapshot(` + Object { + "level": "error", + "message": "Action failed with '[index_not_yellow_timeout] Timeout waiting for ... Refer to repeatedTimeoutRequests for information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", + } + `); }); test('LEGACY_CREATE_REINDEX_TARGET -> LEGACY_REINDEX resets retry count and retry delay if action succeeds', () => { const res: ResponseType<'LEGACY_CREATE_REINDEX_TARGET'> = @@ -743,6 +753,12 @@ describe('migrations v2 model', () => { expect(newState.controlState).toEqual('WAIT_FOR_YELLOW_SOURCE'); expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); + expect(newState.logs[0]).toMatchInlineSnapshot(` + Object { + "level": "error", + "message": "Action failed with '[index_not_yellow_timeout] Timeout waiting for ... Refer to repeatedTimeoutRequests for information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", + } + `); }); test('WAIT_FOR_YELLOW_SOURCE -> CHECK_UNKNOWN_DOCUMENTS resets retry count and delay if action succeeds', () => { @@ -962,6 +978,12 @@ describe('migrations v2 model', () => { expect(newState.controlState).toEqual('CREATE_REINDEX_TEMP'); expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); + expect(newState.logs[0]).toMatchInlineSnapshot(` + Object { + "level": "error", + "message": "Action failed with '[index_not_yellow_timeout] Timeout waiting for ... Refer to repeatedTimeoutRequests for information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", + } + `); }); it('CREATE_REINDEX_TEMP -> REINDEX_SOURCE_TO_TEMP_OPEN_PIT resets retry count if action succeeds', () => { const res: ResponseType<'CREATE_REINDEX_TEMP'> = Either.right('create_index_succeeded'); @@ -1296,6 +1318,12 @@ describe('migrations v2 model', () => { expect(newState.controlState).toEqual('CLONE_TEMP_TO_TARGET'); expect(newState.retryCount).toEqual(1); expect(newState.retryDelay).toEqual(2000); + expect(newState.logs[0]).toMatchInlineSnapshot(` + Object { + "level": "error", + "message": "Action failed with '[index_not_yellow_timeout] Timeout waiting for ... Refer to repeatedTimeoutRequests for information on how to resolve the issue.'. Retrying attempt 1 in 2 seconds.", + } + `); }); it('CREATE_NEW_TARGET -> MARK_VERSION_INDEX_READY resets the retry count and delay', () => { const res: ResponseType<'CLONE_TEMP_TO_TARGET'> = Either.right({ diff --git a/src/core/server/saved_objects/migrations/model/model.ts b/src/core/server/saved_objects/migrations/model/model.ts index cff23f0eeda65..cbd1941720183 100644 --- a/src/core/server/saved_objects/migrations/model/model.ts +++ b/src/core/server/saved_objects/migrations/model/model.ts @@ -25,7 +25,6 @@ import { extractTransformFailuresReason, extractUnknownDocFailureReason, fatalReasonDocumentExceedsMaxBatchSizeBytes, - fatalReasonClusterRoutingAllocationUnsupported, } from './extract_errors'; import type { ExcludeRetryableEsError } from './types'; import { @@ -67,23 +66,9 @@ export const model = (currentState: State, resW: ResponseType): if (Either.isLeft(res)) { const left = res.left; - if (isLeftTypeof(left, 'unsupported_cluster_routing_allocation')) { - const initErrorMessages = fatalReasonClusterRoutingAllocationUnsupported({ - errorMessage: left.message, - docSectionLink: stateP.migrationDocLinks.routingAllocationDisabled, - }); - return { - ...stateP, - controlState: 'FATAL', - reason: initErrorMessages.fatalReason, - logs: [ - ...stateP.logs, - { - level: 'error', - message: initErrorMessages.logsErrorMessage, - }, - ], - }; + if (isLeftTypeof(left, 'incompatible_cluster_routing_allocation')) { + const retryErrorMessage = `[${left.type}] Incompatible Elasticsearch cluster settings detected. Remove the persistent and transient Elasticsearch cluster setting 'cluster.routing.allocation.enable' or set it to a value of 'all' to allow migrations to proceed. Refer to ${stateP.migrationDocLinks.routingAllocationDisabled} for more information on how to resolve the issue.`; + return delayRetryState(stateP, retryErrorMessage, stateP.retryAttempts); } else { return throwBadResponse(stateP, left); } diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts index fbdd962a95298..14fccf09d83b0 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.test.ts @@ -196,6 +196,33 @@ describe('createStreamingBatchedFunction()', () => { }); }); + test("doesn't send batch request if all items have been aborted", async () => { + const { fetchStreaming } = setup(); + const fn = createStreamingBatchedFunction({ + url: '/test', + fetchStreaming, + maxItemAge: 5, + flushOnMaxItems: 3, + getIsCompressionDisabled: () => true, + }); + + const abortController = new AbortController(); + abortController.abort(); + + expect.assertions(3); + const req1 = fn({ foo: 'bar' }, abortController.signal).catch((e) => + expect(e).toBeInstanceOf(AbortError) + ); + const req2 = fn({ baz: 'quix' }, abortController.signal).catch((e) => + expect(e).toBeInstanceOf(AbortError) + ); + + jest.advanceTimersByTime(6); + expect(fetchStreaming).not.toBeCalled(); + + await Promise.all([req1, req2]); + }); + test('sends POST request to correct endpoint with items in array batched sorted in call order', async () => { const { fetchStreaming } = setup(); const fn = createStreamingBatchedFunction({ diff --git a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts index c1618c9d3bbc1..b2e530a3a9bc1 100644 --- a/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts +++ b/src/plugins/bfetch/public/batching/create_streaming_batched_function.ts @@ -88,6 +88,10 @@ export const createStreamingBatchedFunction = ( return !item.signal?.aborted; }); + if (items.length === 0) { + return; // all items have been aborted before a request has been sent + } + const donePromises: Array> = items.map((item) => { return new Promise((resolve) => { const { promise: abortPromise, cleanup } = item.signal diff --git a/src/plugins/bfetch/server/streaming/create_compressed_stream.ts b/src/plugins/bfetch/server/streaming/create_compressed_stream.ts index 5f7319ed154fc..b3c9c8db12699 100644 --- a/src/plugins/bfetch/server/streaming/create_compressed_stream.ts +++ b/src/plugins/bfetch/server/streaming/create_compressed_stream.ts @@ -37,7 +37,7 @@ export const createCompressedStream = ( ): Stream => { const output = new PassThrough(); - const sub = results + results .pipe( concatMap((message: Response) => { const strMessage = JSON.stringify(message); @@ -50,7 +50,6 @@ export const createCompressedStream = ( }), finalize(() => { output.end(); - sub.unsubscribe(); }) ) .subscribe(); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/__snapshots__/xy_vis.test.ts.snap b/src/plugins/chart_expressions/expression_xy/common/expression_functions/__snapshots__/xy_vis.test.ts.snap new file mode 100644 index 0000000000000..4f3ad589f1eea --- /dev/null +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/__snapshots__/xy_vis.test.ts.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`xyVis it should throw error if splitColumnAccessor is pointing to the absent column 1`] = `"Provided column name or index is invalid: absent-accessor"`; + +exports[`xyVis it should throw error if splitRowAccessor is pointing to the absent column 1`] = `"Provided column name or index is invalid: absent-accessor"`; diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/common_data_layer_args.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/common_data_layer_args.ts index 5a1dad533c084..8c060ef4096d7 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/common_data_layer_args.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/common_data_layer_args.ts @@ -27,6 +27,7 @@ export const commonDataLayerArgs: CommonDataLayerFnArgs = { help: strings.getXAccessorHelp(), }, seriesType: { + aliases: ['_'], types: ['string'], options: [...Object.values(SeriesTypes)], help: strings.getSeriesTypeHelp(), diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/extended_data_layer.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/extended_data_layer.ts index 84c1213fc069d..4eb50c3388ec1 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/extended_data_layer.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/extended_data_layer.ts @@ -10,6 +10,7 @@ import { ExtendedDataLayerFn } from '../types'; import { EXTENDED_DATA_LAYER, LayerTypes } from '../constants'; import { strings } from '../i18n'; import { commonDataLayerArgs } from './common_data_layer_args'; +import { getAccessors } from '../helpers'; export const extendedDataLayerFunction: ExtendedDataLayerFn = { name: EXTENDED_DATA_LAYER, @@ -29,12 +30,13 @@ export const extendedDataLayerFunction: ExtendedDataLayerFn = { }, }, fn(input, args) { + const table = args.table ?? input; return { type: EXTENDED_DATA_LAYER, ...args, - accessors: args.accessors ?? [], + ...getAccessors(args, table), layerType: LayerTypes.DATA, - table: args.table ?? input, + table, }; }, }; diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.ts index 6b926e1ceff05..695bd16613715 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/layered_xy_vis.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { i18n } from '@kbn/i18n'; import { LayeredXyVisFn } from '../types'; import { EXTENDED_DATA_LAYER, @@ -26,9 +25,7 @@ export const layeredXyVisFunction: LayeredXyVisFn = { ...commonXYArgs, layers: { types: [EXTENDED_DATA_LAYER, EXTENDED_REFERENCE_LINE_LAYER, EXTENDED_ANNOTATION_LAYER], - help: i18n.translate('expressionXY.layeredXyVis.layers.help', { - defaultMessage: 'Layers of visual series', - }), + help: strings.getLayersHelp(), multi: true, }, }, diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts index 871135dd45bcb..cb6527eb1c393 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.test.ts @@ -33,4 +33,46 @@ describe('xyVis', () => { }, }); }); + + test('it should throw error if splitRowAccessor is pointing to the absent column', async () => { + const { data, args } = sampleArgs(); + const { layers, ...rest } = args; + const { layerId, layerType, table, type, ...restLayerArgs } = sampleLayer; + const splitRowAccessor = 'absent-accessor'; + + expect( + xyVisFunction.fn( + data, + { + ...rest, + ...restLayerArgs, + referenceLineLayers: [], + annotationLayers: [], + splitRowAccessor, + }, + createMockExecutionContext() + ) + ).rejects.toThrowErrorMatchingSnapshot(); + }); + + test('it should throw error if splitColumnAccessor is pointing to the absent column', async () => { + const { data, args } = sampleArgs(); + const { layers, ...rest } = args; + const { layerId, layerType, table, type, ...restLayerArgs } = sampleLayer; + const splitColumnAccessor = 'absent-accessor'; + + expect( + xyVisFunction.fn( + data, + { + ...rest, + ...restLayerArgs, + referenceLineLayers: [], + annotationLayers: [], + splitColumnAccessor, + }, + createMockExecutionContext() + ) + ).rejects.toThrowErrorMatchingSnapshot(); + }); }); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.ts index e8a5858d3ed26..d2aee5048deb3 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis.ts @@ -30,6 +30,14 @@ export const xyVisFunction: XyVisFn = { help: strings.getAnnotationLayerHelp(), multi: true, }, + splitColumnAccessor: { + types: ['vis_dimension', 'string'], + help: strings.getSplitColumnAccessorHelp(), + }, + splitRowAccessor: { + types: ['vis_dimension', 'string'], + help: strings.getSplitRowAccessorHelp(), + }, }, async fn(data, args, handlers) { const { xyVisFn } = await import('./xy_vis_fn'); diff --git a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts index 5e2f7432ddf93..ff217a965fae9 100644 --- a/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts +++ b/src/plugins/chart_expressions/expression_xy/common/expression_functions/xy_vis_fn.ts @@ -6,10 +6,15 @@ * Side Public License, v 1. */ -import { Dimension, prepareLogTable } from '@kbn/visualizations-plugin/common/utils'; +import { + Dimension, + prepareLogTable, + validateAccessor, +} from '@kbn/visualizations-plugin/common/utils'; +import type { Datatable } from '@kbn/expressions-plugin/common'; import { LayerTypes, XY_VIS_RENDERER, DATA_LAYER } from '../constants'; -import { appendLayerIds } from '../helpers'; -import { DataLayerConfigResult, XYLayerConfig, XyVisFn } from '../types'; +import { appendLayerIds, getAccessors } from '../helpers'; +import { DataLayerConfigResult, XYLayerConfig, XyVisFn, XYArgs } from '../types'; import { getLayerDimensions } from '../utils'; import { hasAreaLayer, @@ -20,12 +25,31 @@ import { validateValueLabels, } from './validate'; +const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult => ({ + type: DATA_LAYER, + seriesType: args.seriesType, + hide: args.hide, + columnToLabel: args.columnToLabel, + yScaleType: args.yScaleType, + xScaleType: args.xScaleType, + isHistogram: args.isHistogram, + palette: args.palette, + yConfig: args.yConfig, + layerType: LayerTypes.DATA, + table, + ...getAccessors(args, table), +}); + export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => { + validateAccessor(args.splitRowAccessor, data.columns); + validateAccessor(args.splitColumnAccessor, data.columns); + const { referenceLineLayers = [], annotationLayers = [], + // data_layer args seriesType, - accessors = [], + accessors, xAccessor, hide, splitAccessor, @@ -37,24 +61,9 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => { palette, ...restArgs } = args; - const dataLayers: DataLayerConfigResult[] = [ - { - type: DATA_LAYER, - seriesType, - accessors, - xAccessor, - hide, - splitAccessor, - columnToLabel, - yScaleType, - xScaleType, - isHistogram, - palette, - yConfig, - layerType: LayerTypes.DATA, - table: data, - }, - ]; + + const dataLayers: DataLayerConfigResult[] = [createDataLayer(args, data)]; + const layers: XYLayerConfig[] = [ ...appendLayerIds(dataLayers, 'dataLayers'), ...appendLayerIds(referenceLineLayers, 'referenceLineLayers'), diff --git a/src/plugins/chart_expressions/expression_xy/common/helpers/index.ts b/src/plugins/chart_expressions/expression_xy/common/helpers/index.ts index 55c4136e0c00d..56416261316aa 100644 --- a/src/plugins/chart_expressions/expression_xy/common/helpers/index.ts +++ b/src/plugins/chart_expressions/expression_xy/common/helpers/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export { appendLayerIds } from './layers'; +export { appendLayerIds, getAccessors } from './layers'; diff --git a/src/plugins/chart_expressions/expression_xy/common/helpers/layers.ts b/src/plugins/chart_expressions/expression_xy/common/helpers/layers.ts index d62ea264acb1a..b07a2d2e2a02e 100644 --- a/src/plugins/chart_expressions/expression_xy/common/helpers/layers.ts +++ b/src/plugins/chart_expressions/expression_xy/common/helpers/layers.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { WithLayerId } from '../types'; +import { Datatable, PointSeriesColumnNames } from '@kbn/expressions-plugin/common'; +import { WithLayerId, DataLayerArgs } from '../types'; function isWithLayerId(layer: T): layer is T & WithLayerId { return (layer as T & WithLayerId).layerId ? true : false; @@ -25,3 +26,17 @@ export function appendLayerIds( layerId: isWithLayerId(l) ? l.layerId : generateLayerId(keyword, index), })); } + +export function getAccessors(args: DataLayerArgs, table: Datatable) { + let splitAccessor = args.splitAccessor; + let xAccessor = args.xAccessor; + let accessors = args.accessors ?? []; + if (!splitAccessor && !xAccessor && !(accessors && accessors.length)) { + const y = table.columns.find((column) => column.id === PointSeriesColumnNames.Y)?.id; + xAccessor = table.columns.find((column) => column.id === PointSeriesColumnNames.X)?.id; + splitAccessor = table.columns.find((column) => column.id === PointSeriesColumnNames.COLOR)?.id; + accessors = y ? [y] : []; + } + + return { splitAccessor, xAccessor, accessors }; +} diff --git a/src/plugins/chart_expressions/expression_xy/common/i18n/index.tsx b/src/plugins/chart_expressions/expression_xy/common/i18n/index.tsx index 225f9de0d6a7c..5b5906ac71582 100644 --- a/src/plugins/chart_expressions/expression_xy/common/i18n/index.tsx +++ b/src/plugins/chart_expressions/expression_xy/common/i18n/index.tsx @@ -113,6 +113,18 @@ export const strings = { i18n.translate('expressionXY.xyVis.ariaLabel.help', { defaultMessage: 'Specifies the aria label of the xy chart', }), + getSplitColumnAccessorHelp: () => + i18n.translate('expressionXY.xyVis.splitColumnAccessor.help', { + defaultMessage: 'Specifies split column of the xy chart', + }), + getSplitRowAccessorHelp: () => + i18n.translate('expressionXY.xyVis.splitRowAccessor.help', { + defaultMessage: 'Specifies split row of the xy chart', + }), + getLayersHelp: () => + i18n.translate('expressionXY.layeredXyVis.layers.help', { + defaultMessage: 'Layers of visual series', + }), getDataLayerFnHelp: () => i18n.translate('expressionXY.dataLayer.help', { defaultMessage: `Configure a layer in the xy chart`, diff --git a/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts b/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts index 3cd84fa14682c..174b3e85f3686 100644 --- a/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts +++ b/src/plugins/chart_expressions/expression_xy/common/types/expression_functions.ts @@ -12,6 +12,8 @@ import type { PaletteOutput } from '@kbn/coloring'; import { Datatable, ExpressionFunctionDefinition } from '@kbn/expressions-plugin'; import { LegendSize } from '@kbn/visualizations-plugin/public'; import { EventAnnotationOutput } from '@kbn/event-annotation-plugin/common'; +import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common'; + import { AxisExtentModes, FillStyles, @@ -202,6 +204,8 @@ export interface XYArgs extends DataLayerArgs { hideEndzones?: boolean; valuesInLegend?: boolean; ariaLabel?: string; + splitRowAccessor?: ExpressionValueVisDimension | string; + splitColumnAccessor?: ExpressionValueVisDimension | string; } export interface LayeredXYArgs { @@ -234,10 +238,10 @@ export interface XYProps { yLeftExtent: AxisExtentConfigResult; yRightExtent: AxisExtentConfigResult; legend: LegendConfigResult; - valueLabels: ValueLabelMode; - layers: CommonXYLayerConfig[]; endValue?: EndValue; emphasizeFitting?: boolean; + valueLabels: ValueLabelMode; + layers: CommonXYLayerConfig[]; fittingFunction?: FittingFunction; axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult; tickLabelsVisibilitySettings?: TickLabelsConfigResult; @@ -248,6 +252,8 @@ export interface XYProps { hideEndzones?: boolean; valuesInLegend?: boolean; ariaLabel?: string; + splitRowAccessor?: ExpressionValueVisDimension | string; + splitColumnAccessor?: ExpressionValueVisDimension | string; } export interface AnnotationLayerArgs { diff --git a/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap b/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap index 2bcfb37aca2e5..3eeeee402205a 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap +++ b/src/plugins/chart_expressions/expression_xy/public/components/__snapshots__/xy_chart.test.tsx.snap @@ -426,7 +426,9 @@ exports[`XYChart component it renders area 1`] = ` }, ], Array [ - undefined, + Object { + "id": "string", + }, ], Array [ Object { @@ -968,7 +970,9 @@ exports[`XYChart component it renders bar 1`] = ` }, ], Array [ - undefined, + Object { + "id": "string", + }, ], Array [ Object { @@ -1510,7 +1514,9 @@ exports[`XYChart component it renders horizontal bar 1`] = ` }, ], Array [ - undefined, + Object { + "id": "string", + }, ], Array [ Object { @@ -2052,7 +2058,9 @@ exports[`XYChart component it renders line 1`] = ` }, ], Array [ - undefined, + Object { + "id": "string", + }, ], Array [ Object { @@ -2594,7 +2602,9 @@ exports[`XYChart component it renders stacked area 1`] = ` }, ], Array [ - undefined, + Object { + "id": "string", + }, ], Array [ Object { @@ -3136,7 +3146,9 @@ exports[`XYChart component it renders stacked bar 1`] = ` }, ], Array [ - undefined, + Object { + "id": "string", + }, ], Array [ Object { @@ -3678,7 +3690,9 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = ` }, ], Array [ - undefined, + Object { + "id": "string", + }, ], Array [ Object { @@ -4073,3 +4087,2398 @@ exports[`XYChart component it renders stacked horizontal bar 1`] = ` /> `; + +exports[`XYChart component split chart should render split chart if both, splitRowAccessor and splitColumnAccessor are specified 1`] = ` + + + + + + + + +`; + +exports[`XYChart component split chart should render split chart if splitColumnAccessor is specified 1`] = ` + + + + + + + + +`; + +exports[`XYChart component split chart should render split chart if splitRowAccessor is specified 1`] = ` + + + + + + + + +`; diff --git a/src/plugins/chart_expressions/expression_xy/public/components/legend_action.tsx b/src/plugins/chart_expressions/expression_xy/public/components/legend_action.tsx index da1939f223649..fd7e905eff881 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/legend_action.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/legend_action.tsx @@ -12,7 +12,7 @@ import type { FilterEvent } from '../types'; import type { CommonXYDataLayerConfig } from '../../common'; import type { FormatFactory } from '../types'; import { LegendActionPopover } from './legend_action_popover'; -import { DatatablesWithFormatInfo } from '../helpers'; +import { DatatablesWithFormatInfo, getFormat } from '../helpers'; export const getLegendAction = ( dataLayers: CommonXYDataLayerConfig[], @@ -40,7 +40,7 @@ export const getLegendAction = ( const { table } = layer; const splitColumn = table.columns.find(({ id }) => id === layer.splitAccessor); - const formatter = formatFactory(splitColumn && splitColumn.meta?.params); + const formatter = formatFactory(splitColumn && getFormat(splitColumn.meta)); const rowIndex = table.rows.findIndex((row) => { if (formattedDatatables[layer.layerId]?.formattedColumns[accessor]) { diff --git a/src/plugins/chart_expressions/expression_xy/public/components/split_chart.tsx b/src/plugins/chart_expressions/expression_xy/public/components/split_chart.tsx new file mode 100644 index 0000000000000..f0b2f7f66a00c --- /dev/null +++ b/src/plugins/chart_expressions/expression_xy/public/components/split_chart.tsx @@ -0,0 +1,73 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useCallback } from 'react'; +import { GroupBy, SmallMultiples, Predicate } from '@elastic/charts'; +import { ExpressionValueVisDimension } from '@kbn/visualizations-plugin/common'; +import { getColumnByAccessor, getFormatByAccessor } from '@kbn/visualizations-plugin/common/utils'; +import { Datatable } from '@kbn/expressions-plugin/public'; +import { FormatFactory } from '../types'; + +interface SplitChartProps { + splitColumnAccessor?: ExpressionValueVisDimension | string; + splitRowAccessor?: ExpressionValueVisDimension | string; + columns: Datatable['columns']; + formatFactory: FormatFactory; +} + +const SPLIT_COLUMN = '__split_column__'; +const SPLIT_ROW = '__split_row__'; + +export const SplitChart = ({ + splitColumnAccessor, + splitRowAccessor, + columns, + formatFactory, +}: SplitChartProps) => { + const format = useCallback( + (value: unknown, accessor: ExpressionValueVisDimension | string) => { + const formatParams = getFormatByAccessor(accessor, columns); + const formatter = formatParams ? formatFactory(formatParams) : formatFactory(); + return formatter.convert(value); + }, + [columns, formatFactory] + ); + + const getData = useCallback( + (datum: Record, accessor: ExpressionValueVisDimension | string) => { + const splitColumn = getColumnByAccessor(accessor, columns); + return datum[splitColumn!.id]; + }, + [columns] + ); + + return splitColumnAccessor || splitRowAccessor ? ( + <> + {splitColumnAccessor && ( + getData(datum, splitColumnAccessor)} + sort={Predicate.DataIndex} + format={(value) => format(value, splitColumnAccessor)} + /> + )} + {splitRowAccessor && ( + getData(datum, splitRowAccessor)} + sort={Predicate.DataIndex} + format={(value) => format(value, splitRowAccessor)} + /> + )} + + + ) : null; +}; diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx index 48f2393935413..7f6dcb7a73925 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.test.tsx @@ -15,6 +15,7 @@ import { BarSeries, Fit, GeometryValue, + GroupBy, HorizontalAlignment, LayoutDirection, LineAnnotation, @@ -24,6 +25,7 @@ import { ScaleType, SeriesNameFn, Settings, + SmallMultiples, VerticalAlignment, XYChartSeriesIdentifier, } from '@elastic/charts'; @@ -57,6 +59,7 @@ import { } from '../../common/types'; import { DataLayers } from './data_layers'; import { Annotations } from './annotations'; +import { SplitChart } from './split_chart'; import { LegendSize } from '@kbn/visualizations-plugin/common'; const onClickValue = jest.fn(); @@ -757,6 +760,29 @@ describe('XYChart component', () => { expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined(); }); + test('it renders empty placeholder for no results with references layer', () => { + const { data, args } = sampleArgsWithReferenceLine(); + const emptyDataLayers = args.layers.map((layer) => { + if (layer.type === 'dataLayer') { + return { ...layer, table: { ...data, rows: [] } }; + } else { + return layer; + } + }); + const component = shallow( + + ); + + expect(component.find(BarSeries)).toHaveLength(0); + expect(component.find(EmptyPlaceholder).prop('icon')).toBeDefined(); + }); + test('onBrushEnd returns correct context data for date histogram data', () => { const { args } = sampleArgs(); @@ -2698,4 +2724,94 @@ describe('XYChart component', () => { expect(rectAnnotations.length).toEqual(1); }); }); + + describe('split chart', () => { + const SPLIT_COLUMN = '__split_column__'; + const SPLIT_ROW = '__split_row__'; + + it('should render split chart if splitRowAccessor is specified', () => { + const { args } = sampleArgs(); + const splitRowAccessor = 'b'; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + + const splitChart = component.find(SplitChart); + + expect(splitChart.prop('splitRowAccessor')).toEqual(splitRowAccessor); + + const groupBy = splitChart.dive().find(GroupBy); + const smallMultiples = splitChart.dive().find(SmallMultiples); + + expect(groupBy.at(0).prop('id')).toEqual(SPLIT_ROW); + expect(smallMultiples.prop('splitHorizontally')).toEqual(SPLIT_ROW); + }); + + it('should render split chart if splitColumnAccessor is specified', () => { + const { args } = sampleArgs(); + const splitColumnAccessor = 'b'; + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + + const splitChart = component.find(SplitChart); + + expect(splitChart.prop('splitColumnAccessor')).toEqual(splitColumnAccessor); + + const groupBy = splitChart.dive().find(GroupBy); + const smallMultiples = splitChart.dive().find(SmallMultiples); + + expect(groupBy.at(0).prop('id')).toEqual(SPLIT_COLUMN); + expect(smallMultiples.prop('splitVertically')).toEqual(SPLIT_COLUMN); + }); + + it('should render split chart if both, splitRowAccessor and splitColumnAccessor are specified', () => { + const { args } = sampleArgs(); + const splitColumnAccessor = 'b'; + const splitRowAccessor = 'c'; + + const component = shallow( + + ); + expect(component).toMatchSnapshot(); + + const splitChart = component.find(SplitChart); + + expect(splitChart.prop('splitRowAccessor')).toEqual(splitRowAccessor); + expect(splitChart.prop('splitColumnAccessor')).toEqual(splitColumnAccessor); + + const groupBy = splitChart.dive().find(GroupBy); + const smallMultiples = splitChart.dive().find(SmallMultiples); + + expect(groupBy.at(0).prop('id')).toEqual(SPLIT_COLUMN); + expect(groupBy.at(1).prop('id')).toEqual(SPLIT_ROW); + + expect(smallMultiples.prop('splitVertically')).toEqual(SPLIT_COLUMN); + expect(smallMultiples.prop('splitHorizontally')).toEqual(SPLIT_ROW); + }); + }); }); diff --git a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx index 6e3f142996949..d8ea89def7128 100644 --- a/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/components/xy_chart.tsx @@ -46,6 +46,7 @@ import { Series, getFormattedTablesByLayers, validateExtent, + getFormat, } from '../helpers'; import { getFilteredLayers, @@ -60,6 +61,7 @@ import { getLegendAction } from './legend_action'; import { ReferenceLineAnnotations, computeChartMargins } from './reference_lines'; import { visualizationDefinitions } from '../definitions'; import { CommonXYLayerConfig } from '../../common/types'; +import { SplitChart } from './split_chart'; import { Annotations, getAnnotationsGroupedByInterval, @@ -151,6 +153,8 @@ export function XYChart({ yLeftExtent, yRightExtent, valuesInLegend, + splitColumnAccessor, + splitRowAccessor, } = args; const chartRef = useRef(null); const chartTheme = chartsThemeService.useChartsTheme(); @@ -172,7 +176,7 @@ export function XYChart({ [dataLayers, formatFactory] ); - if (filteredLayers.length === 0) { + if (dataLayers.length === 0) { const icon: IconType = getIconForSeriesType( getDataLayers(layers)?.[0]?.seriesType || SeriesTypes.BAR ); @@ -182,7 +186,7 @@ export function XYChart({ // use formatting hint of first x axis column to format ticks const xAxisColumn = dataLayers[0]?.table.columns.find(({ id }) => id === dataLayers[0].xAccessor); - const xAxisFormatter = formatFactory(xAxisColumn && xAxisColumn.meta?.params); + const xAxisFormatter = formatFactory(xAxisColumn && getFormat(xAxisColumn.meta)); // This is a safe formatter for the xAccessor that abstracts the knowledge of already formatted layers const safeXAccessorLabelRenderer = (value: unknown): string => @@ -253,7 +257,7 @@ export function XYChart({ const annotationsLayers = getAnnotationsLayers(layers); const firstTable = dataLayers[0]?.table; - const xColumnId = firstTable.columns.find((col) => col.id === dataLayers[0]?.xAccessor)?.id; + const xColumnId = firstTable?.columns.find((col) => col.id === dataLayers[0]?.xAccessor)?.id; const groupedLineAnnotations = getAnnotationsGroupedByInterval( annotationsLayers, @@ -381,7 +385,7 @@ export function XYChart({ layer.xAccessor && formattedDatatables[layer.layerId]?.formattedColumns[layer.xAccessor] && xColumn - ? formatFactory(xColumn.meta.params) + ? formatFactory(getFormat(xColumn.meta)) : xAxisFormatter; const rowIndex = table.rows.findIndex((row) => { @@ -406,7 +410,7 @@ export function XYChart({ const pointValue = xySeries.seriesKeys[0]; const splitColumn = table.columns.find(({ id }) => id === layer.splitAccessor); - const splitFormatter = formatFactory(splitColumn && splitColumn.meta?.params); + const splitFormatter = formatFactory(splitColumn && getFormat(splitColumn.meta)); points.push({ row: table.rows.findIndex((row) => { @@ -496,6 +500,9 @@ export function XYChart({ : undefined, }, }; + const isSplitChart = splitColumnAccessor || splitRowAccessor; + const splitTable = isSplitChart ? dataLayers[0].table : undefined; + return ( - + {isSplitChart && splitTable && ( + + )} {yAxesConfiguration.map((axis) => { return ( | undefined = layer.yConfig; const mode = yConfig?.find((yAxisConfig) => yAxisConfig.forAccessor === accessor)?.axisMode || 'auto'; - let formatter: SerializedFieldFormat = table.columns?.find((column) => column.id === accessor) - ?.meta?.params || { id: 'number' }; + const col = table.columns?.find((column) => column.id === accessor); + let formatter: SerializedFieldFormat = col?.meta ? getFormat(col.meta) : { id: 'number' }; if ( isDataLayer(layer) && layer.seriesType.includes('percentage') && diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts index e94d22471aba9..4d141757a225b 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/color_assignment.ts @@ -11,6 +11,7 @@ import { euiLightVars } from '@kbn/ui-theme'; import { FormatFactory } from '../types'; import { isDataLayer } from './visualization'; import { CommonXYDataLayerConfig, CommonXYLayerConfig } from '../../common'; +import { getFormat } from './format'; const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object'; @@ -49,7 +50,7 @@ export function getColorAssignments( } const splitAccessor = layer.splitAccessor; const column = layer.table.columns?.find(({ id }) => id === splitAccessor); - const columnFormatter = column && formatFactory(column.meta.params); + const columnFormatter = column && formatFactory(getFormat(column.meta)); const splits = !column || !layer.table ? [] diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx b/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx index 07af8a3c408c2..2f117c542df16 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/data_layers.tsx @@ -30,6 +30,7 @@ import { FormatFactory } from '../types'; import { getSeriesColor } from './state'; import { ColorAssignments } from './color_assignment'; import { GroupsConfiguration } from './axes_configuration'; +import { getFormat } from './format'; type SeriesSpec = LineSeriesProps & BarSeriesProps & AreaSeriesProps; @@ -115,7 +116,7 @@ export const getFormattedTable = ( xScaleType: XScaleType ): { table: Datatable; formattedColumns: Record } => { const columnsFormatters = table.columns.reduce>( - (formatters, { id, meta }) => ({ ...formatters, [id]: formatFactory(meta.params) }), + (formatters, { id, meta }) => ({ ...formatters, [id]: formatFactory(getFormat(meta)) }), {} ); diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/format.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/format.ts new file mode 100644 index 0000000000000..480aea5d2552b --- /dev/null +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/format.ts @@ -0,0 +1,11 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DatatableColumnMeta } from '@kbn/expressions-plugin'; + +export const getFormat = (meta?: DatatableColumnMeta) => meta?.params || { id: meta?.type }; diff --git a/src/plugins/chart_expressions/expression_xy/public/helpers/index.ts b/src/plugins/chart_expressions/expression_xy/public/helpers/index.ts index 773ae4ee22d94..2fb2af16c08ae 100644 --- a/src/plugins/chart_expressions/expression_xy/public/helpers/index.ts +++ b/src/plugins/chart_expressions/expression_xy/public/helpers/index.ts @@ -16,3 +16,4 @@ export * from './icon'; export * from './color_assignment'; export * from './annotations'; export * from './data_layers'; +export * from './format'; diff --git a/src/plugins/charts/public/static/components/legend_toggle.tsx b/src/plugins/charts/public/static/components/legend_toggle.tsx index 0630fef080323..a4d53e5d54ae6 100644 --- a/src/plugins/charts/public/static/components/legend_toggle.tsx +++ b/src/plugins/charts/public/static/components/legend_toggle.tsx @@ -8,7 +8,7 @@ import React, { memo, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { htmlIdGenerator, EuiButtonIcon, useEuiTheme } from '@elastic/eui'; +import { EuiButtonIcon, useEuiTheme } from '@elastic/eui'; import { Position } from '@elastic/charts'; import { css } from '@emotion/react'; @@ -19,7 +19,6 @@ export interface LegendToggleProps { } const LegendToggleComponent = ({ onClick, showLegend, legendPosition }: LegendToggleProps) => { - const legendId = useMemo(() => htmlIdGenerator()('legend'), []); const { euiTheme } = useEuiTheme(); const baseStyles = useMemo( @@ -65,7 +64,6 @@ const LegendToggleComponent = ({ onClick, showLegend, legendPosition }: LegendTo defaultMessage: 'Toggle legend', })} aria-expanded={showLegend} - aria-controls={legendId} isSelected={showLegend} data-test-subj="vislibToggleLegend" title={i18n.translate('charts.legend.toggleLegendButtonTitle', { diff --git a/src/plugins/controls/public/control_group/editor/control_group_editor.tsx b/src/plugins/controls/public/control_group/editor/control_group_editor.tsx index 8917769f6b151..003468a21e394 100644 --- a/src/plugins/controls/public/control_group/editor/control_group_editor.tsx +++ b/src/plugins/controls/public/control_group/editor/control_group_editor.tsx @@ -14,9 +14,8 @@ * Side Public License, v 1. */ -import { omit } from 'lodash'; import fastIsEqual from 'fast-deep-equal'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { EuiFlyoutHeader, EuiButtonGroup, @@ -31,8 +30,6 @@ import { EuiSpacer, EuiCheckbox, EuiForm, - EuiAccordion, - useGeneratedHtmlId, EuiSwitch, EuiText, EuiHorizontalRule, @@ -68,7 +65,6 @@ export const ControlGroupEditor = ({ onClose, }: EditControlGroupProps) => { const [resetAllWidths, setResetAllWidths] = useState(false); - const advancedSettingsAccordionId = useGeneratedHtmlId({ prefix: 'advancedSettingsAccordion' }); const [controlGroupEditorState, setControlGroupEditorState] = useState({ defaultControlWidth: DEFAULT_CONTROL_WIDTH, @@ -99,14 +95,6 @@ export const ControlGroupEditor = ({ [controlGroupEditorState] ); - const fullQuerySyncActive = useMemo( - () => - !Object.values(omit(controlGroupEditorState.ignoreParentSettings, 'ignoreValidations')).some( - Boolean - ), - [controlGroupEditorState] - ); - const applyChangesToInput = useCallback(() => { const inputToApply = { ...controlGroupEditorState }; if (resetAllWidths) { @@ -177,70 +165,6 @@ export const ControlGroupEditor = ({ - - - - { - const newSetting = !e.target.checked; - updateIgnoreSetting({ - ignoreFilters: newSetting, - ignoreTimerange: newSetting, - ignoreQuery: newSetting, - }); - }} - /> - - - -

{ControlGroupStrings.management.querySync.getQuerySettingsTitle()}

-
- -

{ControlGroupStrings.management.querySync.getQuerySettingsSubtitle()}

-
- - - - - updateIgnoreSetting({ ignoreTimerange: e.target.checked })} - /> - - - updateIgnoreSetting({ ignoreQuery: e.target.checked })} - /> - - - updateIgnoreSetting({ ignoreFilters: e.target.checked })} - /> - - -
-
- diff --git a/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts b/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts index 50b8fe6374f2c..ffa8cb533bc5e 100644 --- a/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts +++ b/src/plugins/data/public/query/filter_manager/lib/get_display_value.test.ts @@ -57,12 +57,12 @@ describe('getFieldDisplayValueFromFilter', () => { jest.resetAllMocks(); }); - it('returns empty string if the index pattern is not found', () => { - const wrongIndexPattern = { - title: 'wrong index pattern', - id: 'wrong index pattern', + it('returns empty string if the data view is not found', () => { + const wrongDataView = { + title: 'wrong data view', + id: 'wrong data view', } as DataView; - const fieldLabel = getFieldDisplayValueFromFilter(phraseFilter, [wrongIndexPattern]); + const fieldLabel = getFieldDisplayValueFromFilter(phraseFilter, [wrongDataView]); expect(fieldLabel).toBe(''); }); diff --git a/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts b/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts index 7f0bbbcfa249c..944a8fc73064a 100644 --- a/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts +++ b/src/plugins/data/public/query/filter_manager/lib/get_display_value.ts @@ -20,8 +20,8 @@ function getValueFormatter(indexPattern?: DataView, key?: string) { if (!field) { throw new Error( i18n.translate('data.filter.filterBar.fieldNotFound', { - defaultMessage: 'Field {key} not found in index pattern {indexPattern}', - values: { key, indexPattern: indexPattern.title }, + defaultMessage: 'Field {key} not found in data view {dataView}', + values: { key, dataView: indexPattern.title }, }) ); } diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_field_search.tsx index f79e45059e87e..4bc2edb9c37dd 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_field_search.tsx @@ -409,10 +409,10 @@ export function DiscoverFieldSearch({ onChange, value, types, presentFieldTypes defaultMessage: 'Learn more about', })}   - +

diff --git a/src/plugins/expressions/common/expression_types/specs/datatable.ts b/src/plugins/expressions/common/expression_types/specs/datatable.ts index 0e1e76acddca4..38b21addd5968 100644 --- a/src/plugins/expressions/common/expression_types/specs/datatable.ts +++ b/src/plugins/expressions/common/expression_types/specs/datatable.ts @@ -186,7 +186,7 @@ export const datatable: ExpressionTypeDefinition { - return { id: colName, name: colName, meta: { type: val.type } }; + return { id: colName, name: val.expression, meta: { type: val.type } }; }), }), }, diff --git a/src/plugins/expressions/common/expression_types/specs/pointseries.ts b/src/plugins/expressions/common/expression_types/specs/pointseries.ts index ef2079bd387a0..b343e91fe707b 100644 --- a/src/plugins/expressions/common/expression_types/specs/pointseries.ts +++ b/src/plugins/expressions/common/expression_types/specs/pointseries.ts @@ -6,16 +6,25 @@ * Side Public License, v 1. */ +import { $Values } from '@kbn/utility-types'; import { ExpressionTypeDefinition, ExpressionValueBoxed } from '../types'; import { Datatable, DatatableRow } from './datatable'; import { ExpressionValueRender } from './render'; const name = 'pointseries'; +export const PointSeriesColumnNames = { + X: 'x', + Y: 'y', + COLOR: 'color', + SIZE: 'size', + TEXT: 'text', +} as const; + /** * Allowed column names in a PointSeries */ -export type PointSeriesColumnName = 'x' | 'y' | 'color' | 'size' | 'text'; +export type PointSeriesColumnName = $Values; /** * Column in a PointSeries diff --git a/src/plugins/expressions/common/index.ts b/src/plugins/expressions/common/index.ts index dcacf49265430..110fc2f5594f9 100644 --- a/src/plugins/expressions/common/index.ts +++ b/src/plugins/expressions/common/index.ts @@ -91,6 +91,7 @@ export { unboxExpressionValue, isDatatable, ExpressionType, + PointSeriesColumnNames, } from './expression_types'; export type { AnyExpressionTypeDefinition, diff --git a/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx b/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx index ae7917e7a1c7a..490d6480b28c9 100644 --- a/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx +++ b/src/plugins/unified_search/public/filter_bar/filter_editor/index.tsx @@ -219,15 +219,15 @@ class FilterEditorUI extends Component { async (results) => { @@ -42,7 +43,13 @@ export function mathAgg(resp, panel, series, meta, extractFields) { }); } else { const percentileMatch = v.field.match(percentileValueMatch); - const m = percentileMatch ? { ...metric, percent: percentileMatch[1] } : { ...metric }; + const m = percentileMatch + ? { + ...metric, + [metric.type === TSVB_METRIC_TYPES.PERCENTILE ? 'percent' : 'value']: + percentileMatch[1], + } + : { ...metric }; acc[v.name] = mapEmptyToZero(m, split.timeseries.buckets); } return acc; diff --git a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.test.js b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.test.js index 777929037f04f..3ff2ee0cba353 100644 --- a/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.test.js +++ b/src/plugins/vis_types/timeseries/server/lib/vis_data/response_processors/series/math.test.js @@ -108,6 +108,64 @@ describe('math(resp, panel, series)', () => { }); }); + test('works with percentiles and percentile rank', async () => { + series.metrics = [ + { + id: 'percentile_cpu', + type: 'percentile', + field: 'cpu', + percentiles: [{ value: 50, id: 'p50' }], + }, + { + id: 'rank_cpu', + type: 'percentile_rank', + field: 'cpu', + percentiles: [{ value: 500, id: 'p500' }], + }, + { + id: 'mathagg', + type: 'math', + script: 'divide(params.a, params.b)', + variables: [ + { name: 'a', field: 'percentile_cpu[50.0]' }, + { name: 'b', field: 'rank_cpu[500.0]' }, + ], + }, + ]; + resp.aggregations.test.buckets[0].timeseries.buckets[0].percentile_cpu = { + values: { '50.0': 0.25 }, + }; + resp.aggregations.test.buckets[0].timeseries.buckets[0].rank_cpu = { + values: { '500.0': 0.125 }, + }; + resp.aggregations.test.buckets[0].timeseries.buckets[1].percentile_cpu = { + values: { '50.0': 0.25 }, + }; + resp.aggregations.test.buckets[0].timeseries.buckets[1].rank_cpu = { + values: { '500.0': 0.25 }, + }; + + const next = await mathAgg(resp, panel, series)((results) => results); + const results = await stdMetric(resp, panel, series)(next)([]); + + expect(results).toHaveLength(1); + + expect(results[0]).toEqual({ + id: 'test╰┄►example-01', + label: 'example-01', + color: 'rgb(255, 0, 0)', + stack: false, + seriesId: 'test', + lines: { show: true, fill: 0, lineWidth: 1, steps: false }, + points: { show: true, radius: 1, lineWidth: 1 }, + bars: { fill: 0, lineWidth: 1, show: false }, + data: [ + [1, 2], + [2, 1], + ], + }); + }); + test('handles math even if there is a series agg', async () => { series.metrics.push({ id: 'myid', diff --git a/test/examples/embeddables/dashboard.ts b/test/examples/embeddables/dashboard.ts index 073b9d9ee3b86..915867ccd57aa 100644 --- a/test/examples/embeddables/dashboard.ts +++ b/test/examples/embeddables/dashboard.ts @@ -116,9 +116,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide }); it('pie charts', async () => { - if (await PageObjects.visChart.isNewChartsLibraryEnabled()) { - await elasticChart.setNewChartUiDebugFlag(); - } + await elasticChart.setNewChartUiDebugFlag(); await pieChart.expectPieSliceCount(5); }); diff --git a/test/functional/apps/dashboard/group1/dashboard_query_bar.ts b/test/functional/apps/dashboard/group1/dashboard_query_bar.ts index 290cc62dca58f..6890ba5bed24f 100644 --- a/test/functional/apps/dashboard/group1/dashboard_query_bar.ts +++ b/test/functional/apps/dashboard/group1/dashboard_query_bar.ts @@ -38,12 +38,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('causes panels to reload when refresh is clicked', async () => { await esArchiver.unload('test/functional/fixtures/es_archiver/dashboard/current/data'); - await queryBar.clickQuerySubmitButton(); await retry.tryForTime(5000, async () => { const headers = await PageObjects.discover.getColumnHeaders(); expect(headers.length).to.be(0); - await pieChart.expectPieSliceCount(0); + await pieChart.expectEmptyPieChart(); }); }); }); diff --git a/test/functional/apps/dashboard/group1/embeddable_rendering.ts b/test/functional/apps/dashboard/group1/embeddable_rendering.ts index 5274a2c12e878..f57b1f1fda83a 100644 --- a/test/functional/apps/dashboard/group1/embeddable_rendering.ts +++ b/test/functional/apps/dashboard/group1/embeddable_rendering.ts @@ -39,7 +39,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { let visNames: string[] = []; const expectAllDataRenders = async () => { - await pieChart.expectPieSliceCount(16); + await pieChart.expectSliceCountForAllPies(16); await dashboardExpect.metricValuesExist(['7,544']); await dashboardExpect.seriesElementCount(14); const tsvbGuageExists = await find.existsByCssSelector('.tvbVisHalfGauge'); @@ -73,7 +73,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }; const expectNoDataRenders = async () => { - await pieChart.expectPieSliceCount(0); + await pieChart.expectEmptyPieChart(); await dashboardExpect.seriesElementCount(0); await dashboardExpect.dataTableNoResult(); await dashboardExpect.savedSearchNoResult(); @@ -112,6 +112,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); await PageObjects.dashboard.clickNewDashboard(); + await elasticChart.setNewChartUiDebugFlag(true); const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; const toTime = 'Apr 13, 2018 @ 00:00:00.000'; @@ -160,6 +161,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('initial render test', async () => { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); + await elasticChart.setNewChartUiDebugFlag(); await expectAllDataRenders(); }); @@ -178,9 +180,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const alert = await browser.getAlert(); await alert?.accept(); - await elasticChart.setNewChartUiDebugFlag(true); - await PageObjects.header.waitUntilLoadingHasFinished(); + await elasticChart.setNewChartUiDebugFlag(); await PageObjects.dashboard.waitForRenderComplete(); await expectAllDataRenders(); }); diff --git a/test/functional/apps/dashboard/group1/legacy_urls.ts b/test/functional/apps/dashboard/group1/legacy_urls.ts index e11da2d82fe47..54834bf8969b7 100644 --- a/test/functional/apps/dashboard/group1/legacy_urls.ts +++ b/test/functional/apps/dashboard/group1/legacy_urls.ts @@ -25,6 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const listingTable = getService('listingTable'); const kibanaServer = getService('kibanaServer'); const security = getService('security'); + const elasticChart = getService('elasticChart'); let kibanaLegacyBaseUrl: string; let kibanaVisualizeBaseUrl: string; @@ -63,6 +64,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const url = `${kibanaLegacyBaseUrl}#/dashboard/${testDashboardId}`; await browser.get(url, true); await PageObjects.header.waitUntilLoadingHasFinished(); + await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.timePicker.setDefaultDataRange(); await PageObjects.dashboard.waitForRenderComplete(); @@ -72,6 +74,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('redirects from legacy hash in wrong app', async () => { const url = `${kibanaVisualizeBaseUrl}#/dashboard/${testDashboardId}`; await browser.get(url, true); + await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.timePicker.setDefaultDataRange(); @@ -111,6 +114,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardAddPanel.addVisualization('legacy url markdown'); (await find.byLinkText('abc')).click(); await PageObjects.header.waitUntilLoadingHasFinished(); + await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.timePicker.setDefaultDataRange(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts b/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts index 966b453409433..bb49225f32d81 100644 --- a/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts +++ b/test/functional/apps/dashboard/group2/dashboard_filter_bar.ts @@ -17,8 +17,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const filterBar = getService('filterBar'); const pieChart = getService('pieChart'); + const elasticChart = getService('elasticChart'); const kibanaServer = getService('kibanaServer'); const browser = getService('browser'); + const queryBar = getService('queryBar'); const security = getService('security'); const PageObjects = getPageObjects([ 'common', @@ -109,6 +111,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.timePicker.setDefaultDataRange(); + await elasticChart.setNewChartUiDebugFlag(true); }); it('are not selected by default', async function () { @@ -119,7 +122,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('are added when a pie chart slice is clicked', async function () { await dashboardAddPanel.addVisualization('Rendering Test: pie'); await PageObjects.dashboard.waitForRenderComplete(); - await pieChart.filterOnPieSlice('4,886'); + await pieChart.filterOnPieSlice('4886'); const filterCount = await filterBar.getFilterCount(); expect(filterCount).to.equal(1); @@ -129,6 +132,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('are preserved after saving a dashboard', async () => { await PageObjects.dashboard.saveDashboard('with filters'); await PageObjects.header.waitUntilLoadingHasFinished(); + await elasticChart.setNewChartUiDebugFlag(true); const filterCount = await filterBar.getFilterCount(); expect(filterCount).to.equal(1); @@ -140,6 +144,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.loadSavedDashboard('with filters'); await PageObjects.header.waitUntilLoadingHasFinished(); + await elasticChart.setNewChartUiDebugFlag(true); + await queryBar.submitQuery(); const filterCount = await filterBar.getFilterCount(); expect(filterCount).to.equal(1); @@ -152,6 +158,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await browser.goForward(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); + await elasticChart.setNewChartUiDebugFlag(true); + await queryBar.submitQuery(); await pieChart.expectPieSliceCount(1); }); diff --git a/test/functional/apps/dashboard/group2/dashboard_filtering.ts b/test/functional/apps/dashboard/group2/dashboard_filtering.ts index 09acbd5965020..9beef9ece92e6 100644 --- a/test/functional/apps/dashboard/group2/dashboard_filtering.ts +++ b/test/functional/apps/dashboard/group2/dashboard_filtering.ts @@ -18,6 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardExpect = getService('dashboardExpect'); const pieChart = getService('pieChart'); const queryBar = getService('queryBar'); + const elasticChart = getService('elasticChart'); const dashboardAddPanel = getService('dashboardAddPanel'); const renderable = getService('renderable'); const testSubjects = getService('testSubjects'); @@ -47,6 +48,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await queryBar.clickQuerySubmitButton(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); + await elasticChart.setNewChartUiDebugFlag(true); }; before(async () => { @@ -85,7 +87,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('filters on pie charts', async () => { - await pieChart.expectPieSliceCount(0); + await pieChart.expectEmptyPieChart(); }); it('area, bar and heatmap charts filtered', async () => { @@ -150,7 +152,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('filters on pie charts', async () => { - await pieChart.expectPieSliceCount(0); + await pieChart.expectEmptyPieChart(); }); it('area, bar and heatmap charts filtered', async () => { @@ -253,6 +255,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('nested filtering', () => { before(async () => { await PageObjects.dashboard.gotoDashboardLandingPage(); + await elasticChart.setNewChartUiDebugFlag(true); }); it('visualization saved with a query filters data', async () => { @@ -323,10 +326,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboardAddPanel.addVisualization( 'Filter Test: animals: linked to search with filter' ); - await pieChart.expectPieSliceCount(7); + await elasticChart.setNewChartUiDebugFlag(true); + await pieChart.expectSliceCountForAllPies(7); }); it('Pie chart linked to saved search filters shows no data with conflicting dashboard query', async () => { + await elasticChart.setNewChartUiDebugFlag(true); await queryBar.setQuery('weightLbs<40'); await queryBar.submitQuery(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/dashboard/group3/bwc_shared_urls.ts b/test/functional/apps/dashboard/group3/bwc_shared_urls.ts index 01b1c8379089e..35d13b715c14c 100644 --- a/test/functional/apps/dashboard/group3/bwc_shared_urls.ts +++ b/test/functional/apps/dashboard/group3/bwc_shared_urls.ts @@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['dashboard', 'header']); const dashboardExpect = getService('dashboardExpect'); const pieChart = getService('pieChart'); + const elasticChart = getService('elasticChart'); const browser = getService('browser'); const log = getService('log'); const queryBar = getService('queryBar'); @@ -41,6 +42,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `legendOpen:!t))),` + `viewMode:edit)`; + const enableNewChartLibraryDebug = async () => { + await elasticChart.setNewChartUiDebugFlag(); + await queryBar.submitQuery(); + }; + describe('bwc shared urls', function describeIndexTests() { before(async function () { await PageObjects.dashboard.initTests(); @@ -75,11 +81,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug(`Navigating to ${url}`); await browser.get(url, true); await PageObjects.header.waitUntilLoadingHasFinished(); + await elasticChart.setNewChartUiDebugFlag(true); const query = await queryBar.getQueryString(); expect(query).to.equal('memory:>220000'); - await pieChart.expectPieSliceCount(0); + await pieChart.expectEmptyPieChart(); await dashboardExpect.panelCount(2); await PageObjects.dashboard.waitForRenderComplete(); }); @@ -92,8 +99,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const url = `${kibanaLegacyBaseUrl}#/dashboard?${urlQuery}`; log.debug(`Navigating to ${url}`); await browser.get(url, true); + enableNewChartLibraryDebug(); await PageObjects.header.waitUntilLoadingHasFinished(); - const query = await queryBar.getQueryString(); expect(query).to.equal('memory:>220000'); @@ -113,6 +120,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug(`Navigating to ${url}`); await browser.get(url, true); await PageObjects.header.waitUntilLoadingHasFinished(); + enableNewChartLibraryDebug(); const query = await queryBar.getQueryString(); expect(query).to.equal('memory:>220000'); @@ -146,8 +154,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug(`Navigating to ${url}`); await browser.get(url, true); + await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.header.waitUntilLoadingHasFinished(); - await dashboardExpect.selectedLegendColorCount('#000000', 5); }); @@ -160,6 +168,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const url = `${kibanaLegacyBaseUrl}#/dashboard?${urlQuery}`; log.debug(`Navigating to ${url}`); await browser.get(url); + await elasticChart.setNewChartUiDebugFlag(true); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); await dashboardExpect.selectedLegendColorCount('#F9D9F9', 5); @@ -169,6 +178,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const newId = await PageObjects.dashboard.getDashboardIdFromCurrentUrl(); expect(newId).to.be.equal(oldId); await PageObjects.dashboard.waitForRenderComplete(); + await elasticChart.setNewChartUiDebugFlag(true); + await queryBar.submitQuery(); await dashboardExpect.selectedLegendColorCount('#000000', 5); }); }); diff --git a/test/functional/apps/dashboard/group3/dashboard_state.ts b/test/functional/apps/dashboard/group3/dashboard_state.ts index c5306f4ab4ff3..79179b7c9d08b 100644 --- a/test/functional/apps/dashboard/group3/dashboard_state.ts +++ b/test/functional/apps/dashboard/group3/dashboard_state.ts @@ -42,7 +42,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('dashboard state', function describeIndexTests() { // Used to track flag before and after reset - let isNewChartsLibraryEnabled = false; + let isNewChartsLibraryEnabled = true; before(async function () { isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); @@ -50,9 +50,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.preserveCrossAppState(); await browser.setLocalStorageItem('data.newDataViewMenu', 'true'); - if (isNewChartsLibraryEnabled) { + if (!isNewChartsLibraryEnabled) { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': false, + 'visualization:visualize:legacyPieChartsLibrary': true, }); } await browser.refresh(); diff --git a/test/functional/apps/dashboard/group3/dashboard_time_picker.ts b/test/functional/apps/dashboard/group3/dashboard_time_picker.ts index 37f6e4f2ef5df..a0554eaea1842 100644 --- a/test/functional/apps/dashboard/group3/dashboard_time_picker.ts +++ b/test/functional/apps/dashboard/group3/dashboard_time_picker.ts @@ -14,6 +14,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardExpect = getService('dashboardExpect'); const pieChart = getService('pieChart'); + const elasticChart = getService('elasticChart'); const dashboardVisualizations = getService('dashboardVisualizations'); const PageObjects = getPageObjects([ 'dashboard', @@ -31,6 +32,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async function () { await PageObjects.dashboard.initTests(); await PageObjects.dashboard.preserveCrossAppState(); + await elasticChart.setNewChartUiDebugFlag(true); }); after(async () => { @@ -43,7 +45,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Visualization updated when time picker changes', async () => { await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations([PIE_CHART_VIS_NAME]); - await pieChart.expectPieSliceCount(0); + await pieChart.expectEmptyPieChart(); await PageObjects.timePicker.setHistoricalDataRange(); await pieChart.expectPieSliceCount(10); @@ -124,6 +126,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { '2015-09-19 06:31:44.000', '2015-09-23 18:31:44.000' ); + await elasticChart.setNewChartUiDebugFlag(true); await pieChart.expectPieSliceCount(10); }); }); diff --git a/test/functional/apps/dashboard/group5/index.ts b/test/functional/apps/dashboard/group5/index.ts index 14f4a6366477d..e05c980dda351 100644 --- a/test/functional/apps/dashboard/group5/index.ts +++ b/test/functional/apps/dashboard/group5/index.ts @@ -25,11 +25,11 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('dashboard app - group 5', function () { // TODO: Remove when vislib is removed // https://github.com/elastic/kibana/issues/56143 - describe('new charts library', function () { + describe('old charts library', function () { before(async () => { await loadLogstash(); await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': false, + 'visualization:visualize:legacyPieChartsLibrary': true, }); await browser.refresh(); }); @@ -37,7 +37,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { after(async () => { await unloadLogstash(); await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': true, + 'visualization:visualize:legacyPieChartsLibrary': false, }); await browser.refresh(); }); diff --git a/test/functional/apps/dashboard_elements/controls/options_list.ts b/test/functional/apps/dashboard_elements/controls/options_list.ts index de3144d98beab..e9afe1a254cdb 100644 --- a/test/functional/apps/dashboard_elements/controls/options_list.ts +++ b/test/functional/apps/dashboard_elements/controls/options_list.ts @@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const queryBar = getService('queryBar'); const pieChart = getService('pieChart'); + const elasticChart = getService('elasticChart'); const filterBar = getService('filterBar'); const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); @@ -32,6 +33,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await dashboard.gotoDashboardLandingPage(); await dashboard.clickNewDashboard(); await timePicker.setDefaultDataRange(); + await elasticChart.setNewChartUiDebugFlag(); }); describe('Options List Control Editor selects relevant data views', async () => { @@ -262,46 +264,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - describe('Does not apply query settings to controls', async () => { - before(async () => { - await dashboardControls.updateAllQuerySyncSettings(false); - }); - - after(async () => { - await dashboardControls.updateAllQuerySyncSettings(true); - }); - - it('Does not apply query to options list control', async () => { - await queryBar.setQuery('isDog : true '); - await queryBar.submitQuery(); - await dashboard.waitForRenderComplete(); - await header.waitUntilLoadingHasFinished(); - await ensureAvailableOptionsEql(allAvailableOptions); - await queryBar.setQuery(''); - await queryBar.submitQuery(); - }); - - it('Does not apply filters to options list control', async () => { - await filterBar.addFilter('sound.keyword', 'is one of', ['bark', 'bow ow ow', 'ruff']); - await dashboard.waitForRenderComplete(); - await header.waitUntilLoadingHasFinished(); - await ensureAvailableOptionsEql(allAvailableOptions); - await filterBar.removeFilter('sound.keyword'); - }); - - it('Does not apply time range to options list control', async () => { - // set time range to time with no documents - await timePicker.setAbsoluteRange( - 'Jan 1, 2017 @ 00:00:00.000', - 'Jan 1, 2017 @ 00:00:00.000' - ); - await dashboard.waitForRenderComplete(); - await header.waitUntilLoadingHasFinished(); - await ensureAvailableOptionsEql(allAvailableOptions); - await timePicker.setDefaultDataRange(); - }); - }); - describe('Selections made in control apply to dashboard', async () => { it('Shows available options in options list', async () => { await ensureAvailableOptionsEql(allAvailableOptions); diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts index 426713c912e88..8cdf4a5f2ff76 100644 --- a/test/functional/apps/getting_started/_shakespeare.ts +++ b/test/functional/apps/getting_started/_shakespeare.ts @@ -39,7 +39,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // order they are added. let aggIndex = 1; // Used to track flag before and after reset - let isNewChartsLibraryEnabled = false; + let isNewChartsLibraryEnabled = true; before(async function () { log.debug( @@ -56,9 +56,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'test/functional/fixtures/es_archiver/getting_started/shakespeare' ); - if (isNewChartsLibraryEnabled) { + if (!isNewChartsLibraryEnabled) { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': false, + 'visualization:visualize:legacyPieChartsLibrary': true, }); await browser.refresh(); } diff --git a/test/functional/apps/getting_started/index.ts b/test/functional/apps/getting_started/index.ts index a0506ce52e028..dfda371c3eedf 100644 --- a/test/functional/apps/getting_started/index.ts +++ b/test/functional/apps/getting_started/index.ts @@ -18,17 +18,17 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); // TODO: Remove when vislib is removed - describe('new charts library', function () { + describe('old charts library', function () { before(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': false, + 'visualization:visualize:legacyPieChartsLibrary': true, }); await browser.refresh(); }); after(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': true, + 'visualization:visualize:legacyPieChartsLibrary': false, }); await browser.refresh(); }); @@ -36,7 +36,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_shakespeare')); }); - describe('', () => { + describe('new charts library', () => { loadTestFile(require.resolve('./_shakespeare')); }); }); diff --git a/test/functional/apps/visualize/group3/_pie_chart.ts b/test/functional/apps/visualize/group3/_pie_chart.ts index 23b008c690cba..e971daa18c8cb 100644 --- a/test/functional/apps/visualize/group3/_pie_chart.ts +++ b/test/functional/apps/visualize/group3/_pie_chart.ts @@ -50,7 +50,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { log.debug('setNumericInterval 4000'); await PageObjects.visEditor.setInterval('40000', { type: 'numeric' }); log.debug('clickGo'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); }); it('should save and load', async function () { @@ -65,7 +65,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('should show 10 slices in pie chart', async function () { - pieChart.expectPieSliceCount(10); + pieChart.expectPieSliceCount(10, isNewChartsLibraryEnabled); }); it('should show correct data', async function () { @@ -105,8 +105,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.toggleOtherBucket(2); await PageObjects.visEditor.toggleMissingBucket(2); log.debug('clickGo'); - await PageObjects.visEditor.clickGo(); - await pieChart.expectPieChartLabels(expectedTableData); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); }); it('should apply correct filter on other bucket', async () => { @@ -114,7 +114,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await pieChart.filterOnPieSlice('Other'); await PageObjects.visChart.waitForVisualization(); - await pieChart.expectPieChartLabels(expectedTableData); + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); await filterBar.removeFilter('machine.os.raw'); await PageObjects.visChart.waitForVisualization(); }); @@ -124,7 +124,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visChart.filterLegend('Other'); await PageObjects.visChart.waitForVisualization(); - await pieChart.expectPieChartLabels(expectedTableData); + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); await filterBar.removeFilter('machine.os.raw'); await PageObjects.visChart.waitForVisualization(); }); @@ -183,8 +183,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.toggleOtherBucket(3); await PageObjects.visEditor.toggleMissingBucket(3); log.debug('clickGo'); - await PageObjects.visEditor.clickGo(); - await pieChart.expectPieChartLabels(expectedTableData); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); }); }); @@ -201,9 +201,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Terms'); await PageObjects.visEditor.selectField('machine.os.raw'); await PageObjects.visEditor.toggleDisabledAgg(2); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); - await pieChart.expectPieChartLabels(expectedTableData); + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); }); it('should correctly save disabled agg', async () => { @@ -213,12 +213,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visChart.waitForRenderingCount(); const expectedTableData = ['ios', 'osx', 'win 7', 'win 8', 'win xp']; - await pieChart.expectPieChartLabels(expectedTableData); + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); }); it('should show correct result when agg is re-enabled', async () => { await PageObjects.visEditor.toggleDisabledAgg(2); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); const expectedTableData = [ '0', @@ -283,7 +283,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'osx', ].sort(); - await pieChart.expectPieChartLabels(expectedTableData); + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); }); }); @@ -304,7 +304,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.addNewFilterAggregation(); log.debug('Set the 2nd filter value'); await PageObjects.visEditor.setFilterAggregationValue('geo.dest:"CN"', 1); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); const emptyFromTime = 'Sep 19, 2016 @ 06:31:44.000'; const emptyToTime = 'Sep 23, 2016 @ 18:31:44.000'; log.debug( @@ -346,7 +346,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickBucket('Split slices'); await PageObjects.visEditor.selectAggregation('Terms'); await PageObjects.visEditor.selectField('geo.dest'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); }); it('should show correct chart', async () => { @@ -435,16 +435,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { '360,000', 'CN', ].sort(); - if (await PageObjects.visChart.isNewLibraryChart('partitionVisChart')) { + if (isNewChartsLibraryEnabled) { await PageObjects.visEditor.clickOptionsTab(); await PageObjects.visEditor.togglePieLegend(); await PageObjects.visEditor.togglePieNestedLegend(); await PageObjects.visEditor.clickDataTab(); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); } await PageObjects.visChart.filterLegend('CN'); await PageObjects.visChart.waitForVisualization(); - await pieChart.expectPieChartLabels(expectedTableData); + await pieChart.expectPieChartLabels(expectedTableData, isNewChartsLibraryEnabled); await filterBar.removeFilter('geo.dest'); await PageObjects.visChart.waitForVisualization(); }); @@ -474,7 +474,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.selectAggregation('Filters'); log.debug('Set the 1st filter value of the aggregation id 3'); await PageObjects.visEditor.setFilterAggregationValue('geo.dest:"UX"', 0, 3); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); const legends = await PageObjects.visChart.getLegendEntries(); const expectedLegends = ['geo.dest:"US"', 'geo.dest:"UX"']; expect(legends).to.eql(expectedLegends); @@ -496,7 +496,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.visEditor.clickBucket('Split slices'); await PageObjects.visEditor.selectAggregation('Terms'); await PageObjects.visEditor.selectField('geo.src'); - await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickGo(isNewChartsLibraryEnabled); }); it('shows correct split chart', async () => { diff --git a/test/functional/apps/visualize/group3/index.ts b/test/functional/apps/visualize/group3/index.ts index 93eff60575cb3..e1f86ca1798a6 100644 --- a/test/functional/apps/visualize/group3/index.ts +++ b/test/functional/apps/visualize/group3/index.ts @@ -12,6 +12,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); const log = getService('log'); const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); describe('visualize app', () => { before(async () => { @@ -21,13 +22,25 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/logstash_functional'); await esArchiver.loadIfNeeded('test/functional/fixtures/es_archiver/long_window_logstash'); + + await kibanaServer.uiSettings.update({ + 'visualization:visualize:legacyPieChartsLibrary': true, + }); + await browser.refresh(); + }); + + after(async () => { + await kibanaServer.uiSettings.update({ + 'visualization:visualize:legacyPieChartsLibrary': false, + }); + await browser.refresh(); }); - loadTestFile(require.resolve('./_pie_chart')); loadTestFile(require.resolve('./_shared_item')); loadTestFile(require.resolve('./_lab_mode')); loadTestFile(require.resolve('./_linked_saved_searches')); loadTestFile(require.resolve('./_visualize_listing')); loadTestFile(require.resolve('./_add_to_dashboard.ts')); + loadTestFile(require.resolve('./_pie_chart')); }); } diff --git a/test/functional/apps/visualize/replaced_vislib_chart_types/index.ts b/test/functional/apps/visualize/replaced_vislib_chart_types/index.ts index 5794edef68555..d7f674753058d 100644 --- a/test/functional/apps/visualize/replaced_vislib_chart_types/index.ts +++ b/test/functional/apps/visualize/replaced_vislib_chart_types/index.ts @@ -27,7 +27,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': false, 'visualization:visualize:legacyHeatmapChartsLibrary': false, }); await browser.refresh(); @@ -35,7 +34,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { after(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:legacyPieChartsLibrary': true, 'visualization:visualize:legacyHeatmapChartsLibrary': true, }); await browser.refresh(); diff --git a/test/functional/config.base.js b/test/functional/config.base.js index 40b50da505951..a7ee20631efa3 100644 --- a/test/functional/config.base.js +++ b/test/functional/config.base.js @@ -39,7 +39,6 @@ export default async function ({ readConfigFile }) { defaults: { 'accessibility:disableAnimations': true, 'dateFormat:tz': 'UTC', - 'visualization:visualize:legacyPieChartsLibrary': true, 'visualization:useLegacyTimeAxis': true, }, }, diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index 3eec4e2ce1a2b..93e811ef5b6a2 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -32,6 +32,10 @@ export class VisualizeChartPageObject extends FtrService { return await this.elasticChart.getChartDebugData(chartSelector); } + public async getAllESChartsDebugDataByTestSubj(chartSelector: string) { + return await this.elasticChart.getAllChartsDebugDataByTestSubj(chartSelector); + } + /** * Is new charts library advanced setting enabled */ diff --git a/test/functional/services/dashboard/expectations.ts b/test/functional/services/dashboard/expectations.ts index c56e7c1eae27e..081e5cd5b85c0 100644 --- a/test/functional/services/dashboard/expectations.ts +++ b/test/functional/services/dashboard/expectations.ts @@ -19,6 +19,7 @@ export class DashboardExpectService extends FtrService { private readonly dashboard = this.ctx.getPageObject('dashboard'); private readonly visChart = this.ctx.getPageObject('visChart'); + private readonly pieChart = this.ctx.getService('pieChart'); private readonly tagCloud = this.ctx.getPageObject('tagCloud'); private readonly findTimeout = 2500; @@ -39,11 +40,11 @@ export class DashboardExpectService extends FtrService { async selectedLegendColorCount(color: string, expectedCount: number) { this.log.debug(`DashboardExpect.selectedLegendColorCount(${color}, ${expectedCount})`); await this.retry.try(async () => { - const selectedLegendColor = await this.testSubjects.findAll( - `legendSelectedColor-${color}`, - this.findTimeout - ); - expect(selectedLegendColor.length).to.be(expectedCount); + const slicesColors = await this.pieChart.getAllPieSlicesColors(); + const selectedColors = slicesColors.filter((sliceColor) => { + return sliceColor === color; + }); + expect(selectedColors.length).to.be(expectedCount); }); } diff --git a/test/functional/services/visualizations/elastic_chart.ts b/test/functional/services/visualizations/elastic_chart.ts index b954b4ba03616..c3ba8697c2256 100644 --- a/test/functional/services/visualizations/elastic_chart.ts +++ b/test/functional/services/visualizations/elastic_chart.ts @@ -94,6 +94,11 @@ export class ElasticChartService extends FtrService { } } + public async getAllChartsDebugDataByTestSubj(dataTestSubj: string): Promise { + const charts = await this.testSubjects.findAll(dataTestSubj); + return charts; + } + private async getAllCharts(timeout?: number) { return await this.find.allByCssSelector('.echChart', timeout); } diff --git a/test/functional/services/visualizations/pie_chart.ts b/test/functional/services/visualizations/pie_chart.ts index 16133140e4abf..0669bb6e91e52 100644 --- a/test/functional/services/visualizations/pie_chart.ts +++ b/test/functional/services/visualizations/pie_chart.ts @@ -35,7 +35,7 @@ export class PieChartService extends FtrService { if (name === 'Other') { sliceLabel = '__other__'; } - const pieSlice = slices.find((slice) => slice.name === sliceLabel); + const pieSlice = slices.find((slice) => String(slice.name) === sliceLabel); const pie = await this.testSubjects.find(partitionVisChartSelector); if (pieSlice) { const pieSize = await pie.getSize(); @@ -101,6 +101,17 @@ export class PieChartService extends FtrService { return await pieSlice.getAttribute('style'); } + async getAllPieSlicesColors() { + const slicesColors = []; + const slices = + (await this.visChart.getEsChartDebugState(partitionVisChartSelector))?.partition?.[0] + ?.partitions ?? []; + for (const slice of slices) { + slicesColors.push(slice.color); + } + return slicesColors; + } + async getAllPieSliceColor(name: string) { this.log.debug(`VisualizePage.getAllPieSliceColor(${name})`); if (await this.visChart.isNewLibraryChart(partitionVisChartSelector)) { @@ -142,8 +153,8 @@ export class PieChartService extends FtrService { await this.inspector.expectTableData(expectedTableData); } - async getPieChartLabels() { - if (await this.visChart.isNewLibraryChart(partitionVisChartSelector)) { + async getPieChartLabels(isNewLibrary: boolean = true) { + if (isNewLibrary) { const slices = (await this.visChart.getEsChartDebugState(partitionVisChartSelector))?.partition?.[0] ?.partitions ?? []; @@ -167,9 +178,9 @@ export class PieChartService extends FtrService { ); } - async getPieSliceCount() { + async getPieSliceCount(isNewLibrary: boolean = true) { this.log.debug('PieChart.getPieSliceCount'); - if (await this.visChart.isNewLibraryChart(partitionVisChartSelector)) { + if (isNewLibrary) { const slices = (await this.visChart.getEsChartDebugState(partitionVisChartSelector))?.partition?.[0] ?.partitions ?? []; @@ -179,6 +190,24 @@ export class PieChartService extends FtrService { return slices.length; } + async getSliceCountForAllPies() { + let pieSlices = 0; + const charts = + (await this.visChart.getAllESChartsDebugDataByTestSubj(partitionVisChartSelector)) ?? []; + for (const chart of charts) { + const visContainer = await chart.findByCssSelector('.echChartStatus'); + const debugDataString: string | undefined = await visContainer.getAttribute( + 'data-ech-debug-state' + ); + if (debugDataString) { + const parsedData = JSON.parse(debugDataString); + const partition = parsedData?.partition?.[0] ?? []; + pieSlices += partition.partitions.length; + } + } + return pieSlices; + } + async expectPieSliceCountEsCharts(expectedCount: number) { const slices = (await this.visChart.getEsChartDebugState(partitionVisChartSelector))?.partition?.[0] @@ -186,18 +215,30 @@ export class PieChartService extends FtrService { expect(slices.length).to.be(expectedCount); } - async expectPieSliceCount(expectedCount: number) { + async expectPieSliceCount(expectedCount: number, isNewLibrary: boolean = true) { this.log.debug(`PieChart.expectPieSliceCount(${expectedCount})`); await this.retry.try(async () => { - const slicesCount = await this.getPieSliceCount(); + const slicesCount = await this.getPieSliceCount(isNewLibrary); + expect(slicesCount).to.be(expectedCount); + }); + } + + async expectSliceCountForAllPies(expectedCount: number) { + await this.retry.try(async () => { + const slicesCount = await this.getSliceCountForAllPies(); expect(slicesCount).to.be(expectedCount); }); } - async expectPieChartLabels(expectedLabels: string[]) { + async expectEmptyPieChart() { + const noResult = await this.testSubjects.exists('partitionVisEmptyValues'); + expect(noResult).to.be(true); + } + + async expectPieChartLabels(expectedLabels: string[], isNewLibrary: boolean = true) { this.log.debug(`PieChart.expectPieChartLabels(${expectedLabels.join(',')})`); await this.retry.try(async () => { - const pieData = await this.getPieChartLabels(); + const pieData = await this.getPieChartLabels(isNewLibrary); expect(pieData.sort()).to.eql(expectedLabels); }); } diff --git a/x-pack/build_chromium/build.py b/x-pack/build_chromium/build.py index f4338ab2c6db1..fd5c1d3f16a65 100644 --- a/x-pack/build_chromium/build.py +++ b/x-pack/build_chromium/build.py @@ -20,6 +20,8 @@ src_path = path.abspath(path.join(os.curdir, 'chromium', 'src')) build_path = path.abspath(path.join(src_path, '..', '..')) +en_us_locale_pak_file_name = 'en-US.pak' +en_us_locale_file_path = path.abspath(en_us_locale_pak_file_name) build_chromium_path = path.abspath(path.dirname(__file__)) argsgn_file = path.join(build_chromium_path, platform.system().lower(), 'args.gn') @@ -35,6 +37,9 @@ if arch_name != 'x64' and arch_name != 'arm64': raise Exception('Unexpected architecture: ' + arch_name + '. `x64` and `arm64` are supported.') +print('Fetching locale files') +runcmd('gsutil cp gs://headless_shell_staging/en-US.pak .') + print('Building Chromium ' + source_version + ' for ' + arch_name + ' from ' + src_path) print('src path: ' + src_path) print('depot_tools path: ' + path.join(build_path, 'depot_tools')) @@ -104,17 +109,13 @@ print('Creating ' + path.join(src_path, zip_filename)) archive = zipfile.ZipFile(zip_filename, mode='w', compression=zipfile.ZIP_DEFLATED) -def archive_file(name): - """A little helper function to write individual files to the zip file""" - from_path = path.join('out/headless', name) - to_path = path.join('headless_shell-' + platform.system().lower() + '_' + arch_name, name) - archive.write(from_path, to_path) - return to_path +path_prefix = 'headless_shell-' + platform.system().lower() + '_' + arch_name # Add dependencies that must be bundled with the Chromium executable. -archive_file('headless_shell') -archive_file(path.join('swiftshader', 'libEGL.so')) -archive_file(path.join('swiftshader', 'libGLESv2.so')) +archive.write('out/headless/headless_shell', path.join(path_prefix, 'headless_shell')) +archive.write('out/headless/libEGL.so', path.join(path_prefix, 'libEGL.so')) +archive.write('out/headless/libGLESv2.so', path.join(path_prefix, 'libGLESv2.so')) +archive.write(en_us_locale_file_path, path.join(path_prefix, 'locales', en_us_locale_pak_file_name)) archive.close() diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js index 9495c599e73d4..c34a6e0b9cbd9 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/file_data_visualizer_view/file_data_visualizer_view.js @@ -65,7 +65,6 @@ export class FileDataVisualizerView extends Component { linesToSample: DEFAULT_LINES_TO_SAMPLE, }; - this.savedObjectsClient = props.savedObjectsClient; this.maxFileUploadBytes = props.fileUpload.getMaxBytes(); } @@ -367,7 +366,6 @@ export class FileDataVisualizerView extends Component { dataViewsContract={this.props.dataViewsContract} showBottomBar={this.showBottomBar} hideBottomBar={this.hideBottomBar} - savedObjectsClient={this.savedObjectsClient} fileUpload={this.props.fileUpload} getAdditionalLinks={this.props.getAdditionalLinks} capabilities={this.props.capabilities} diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js index 006bd9e3356f7..5aaeafe563e1d 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/components/import_view/import_view.js @@ -77,7 +77,7 @@ export class ImportView extends Component { super(props); this.state = getDefaultState(DEFAULT_STATE, this.props.results, this.props.capabilities); - this.savedObjectsClient = props.savedObjectsClient; + this.dataViewsContract = props.dataViewsContract; } componentDidMount() { @@ -417,14 +417,7 @@ export class ImportView extends Component { async loadDataViewNames() { try { - const dataViewNames = ( - await this.savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title'], - perPage: 10000, - }) - ).savedObjects.map(({ attributes }) => attributes && attributes.title); - + const dataViewNames = await this.dataViewsContract.getTitles(); this.setState({ dataViewNames }); } catch (error) { console.error('failed to load data views', error); diff --git a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx index 0238659b1f348..edd5ad4bbe1b2 100644 --- a/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx +++ b/x-pack/plugins/data_visualizer/public/application/file_data_visualizer/file_data_visualizer.tsx @@ -42,7 +42,6 @@ export const FileDataVisualizer: FC = ({ getAdditionalLinks }) => { ({ { type: 'function', function: 'mosaicVis', - arguments: generateCommonArguments(...rest), + arguments: { + ...generateCommonArguments(...rest), + // flip order of bucket dimensions so the rows are fetched before the columns to keep them stable + buckets: rest[2] + .reverse() + .map((o) => o.columnId) + .map(prepareDimension), + }, }, ], }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx deleted file mode 100644 index ed6e30947779b..0000000000000 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.tsx +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import React from 'react'; -import type { Map as MbMap } from '@kbn/mapbox-gl'; -import { DynamicStyleProperty } from './dynamic_style_property'; -import { OrdinalLegend } from '../components/legend/ordinal_legend'; -import { makeMbClampedNumberExpression } from '../style_util'; -import { - FieldFormatter, - HALF_MAKI_ICON_SIZE, - MB_LOOKUP_FUNCTION, - VECTOR_STYLES, -} from '../../../../../common/constants'; -import { SizeDynamicOptions } from '../../../../../common/descriptor_types'; -import { IField } from '../../../fields/field'; -import { IVectorLayer } from '../../../layers/vector_layer'; - -export class DynamicSizeProperty extends DynamicStyleProperty { - private readonly _isSymbolizedAsIcon: boolean; - - constructor( - options: SizeDynamicOptions, - styleName: VECTOR_STYLES, - field: IField | null, - vectorLayer: IVectorLayer, - getFieldFormatter: (fieldName: string) => null | FieldFormatter, - isSymbolizedAsIcon: boolean - ) { - super(options, styleName, field, vectorLayer, getFieldFormatter); - this._isSymbolizedAsIcon = isSymbolizedAsIcon; - } - - supportsFeatureState() { - // mb style "icon-size" does not support feature state - if (this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon) { - return false; - } - - // mb style "text-size" does not support feature state - if (this.getStyleName() === VECTOR_STYLES.LABEL_SIZE) { - return false; - } - - return true; - } - - syncHaloWidthWithMb(mbLayerId: string, mbMap: MbMap) { - const haloWidth = this.getMbSizeExpression(); - mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', haloWidth); - } - - syncIconSizeWithMb(symbolLayerId: string, mbMap: MbMap) { - const rangeFieldMeta = this.getRangeFieldMeta(); - if (this._isSizeDynamicConfigComplete() && rangeFieldMeta) { - const targetName = this.getMbFieldName(); - // Using property state instead of feature-state because layout properties do not support feature-state - mbMap.setLayoutProperty(symbolLayerId, 'icon-size', [ - 'interpolate', - ['linear'], - makeMbClampedNumberExpression({ - minValue: rangeFieldMeta.min, - maxValue: rangeFieldMeta.max, - fallback: 0, - lookupFunction: MB_LOOKUP_FUNCTION.GET, - fieldName: targetName, - }), - rangeFieldMeta.min, - this._options.minSize / HALF_MAKI_ICON_SIZE, - rangeFieldMeta.max, - this._options.maxSize / HALF_MAKI_ICON_SIZE, - ]); - } else { - mbMap.setLayoutProperty(symbolLayerId, 'icon-size', null); - } - } - - syncCircleStrokeWidthWithMb(mbLayerId: string, mbMap: MbMap) { - const lineWidth = this.getMbSizeExpression(); - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); - } - - syncCircleRadiusWithMb(mbLayerId: string, mbMap: MbMap) { - const circleRadius = this.getMbSizeExpression(); - mbMap.setPaintProperty(mbLayerId, 'circle-radius', circleRadius); - } - - syncLineWidthWithMb(mbLayerId: string, mbMap: MbMap) { - const lineWidth = this.getMbSizeExpression(); - mbMap.setPaintProperty(mbLayerId, 'line-width', lineWidth); - } - - syncLabelSizeWithMb(mbLayerId: string, mbMap: MbMap) { - const lineWidth = this.getMbSizeExpression(); - mbMap.setLayoutProperty(mbLayerId, 'text-size', lineWidth); - } - - getMbSizeExpression() { - const rangeFieldMeta = this.getRangeFieldMeta(); - if (!this._isSizeDynamicConfigComplete() || !rangeFieldMeta) { - // return min of size to avoid flashing - // returning minimum allows "growing" of the symbols when the meta comes in - // A grow effect us less visually jarring as shrinking. - // especially relevant when displaying fine-grained grids using mvt - return this._options.minSize >= 0 ? this._options.minSize : null; - } - - return this._getMbDataDrivenSize({ - targetName: this.getMbFieldName(), - minSize: this._options.minSize, - maxSize: this._options.maxSize, - minValue: rangeFieldMeta.min, - maxValue: rangeFieldMeta.max, - }); - } - - _getMbDataDrivenSize({ - targetName, - minSize, - maxSize, - minValue, - maxValue, - }: { - targetName: string; - minSize: number; - maxSize: number; - minValue: number; - maxValue: number; - }) { - const stops = - minValue === maxValue ? [maxValue, maxSize] : [minValue, minSize, maxValue, maxSize]; - return [ - 'interpolate', - ['linear'], - makeMbClampedNumberExpression({ - lookupFunction: this.getMbLookupFunction(), - maxValue, - minValue, - fieldName: targetName, - fallback: 0, - }), - ...stops, - ]; - } - - _isSizeDynamicConfigComplete() { - return ( - this._field && - this._field.isValid() && - this._options.minSize >= 0 && - this._options.maxSize >= 0 - ); - } - - renderLegendDetailRow() { - return ; - } -} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap similarity index 100% rename from x-pack/plugins/maps/public/classes/styles/vector/properties/__snapshots__/dynamic_size_property.test.tsx.snap rename to x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/__snapshots__/dynamic_size_property.test.tsx.snap diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.test.tsx new file mode 100644 index 0000000000000..0446b9e30f47b --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.test.tsx @@ -0,0 +1,134 @@ +/* + * 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. + */ + +jest.mock('../../components/vector_style_editor', () => ({ + VectorStyleEditor: () => { + return
mockVectorStyleEditor
; + }, +})); + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { DynamicSizeProperty } from './dynamic_size_property'; +import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../../common/constants'; +import { IField } from '../../../../fields/field'; +import { IVectorLayer } from '../../../../layers/vector_layer'; + +describe('renderLegendDetailRow', () => { + test('Should render as range', async () => { + const field = { + getLabel: async () => { + return 'foobar_label'; + }, + getName: () => { + return 'foodbar'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + supportsFieldMetaFromEs: () => { + return true; + }, + supportsFieldMetaFromLocalData: () => { + return true; + }, + } as unknown as IField; + const sizeProp = new DynamicSizeProperty( + { minSize: 0, maxSize: 10, fieldMetaOptions: { isEnabled: true } }, + VECTOR_STYLES.ICON_SIZE, + field, + {} as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + }, + false + ); + sizeProp.getRangeFieldMeta = () => { + return { + min: 0, + max: 100, + delta: 100, + }; + }; + + const legendRow = sizeProp.renderLegendDetailRow(); + const component = shallow(legendRow); + + // Ensure all promises resolve + await new Promise((resolve) => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + expect(component).toMatchSnapshot(); + }); +}); + +describe('getMbSizeExpression', () => { + test('Should return interpolation expression with single stop when range.delta is 0', async () => { + const field = { + isValid: () => { + return true; + }, + getName: () => { + return 'foobar'; + }, + getMbFieldName: () => { + return 'foobar'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + getSource: () => { + return { + isMvt: () => { + return false; + }, + }; + }, + supportsFieldMetaFromEs: () => { + return true; + }, + } as unknown as IField; + const sizeProp = new DynamicSizeProperty( + { minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } }, + VECTOR_STYLES.ICON_SIZE, + field, + {} as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + }, + false + ); + sizeProp.getRangeFieldMeta = () => { + return { + min: 100, + max: 100, + delta: 0, + }; + }; + + expect(sizeProp.getMbSizeExpression()).toEqual([ + 'interpolate', + ['linear'], + [ + 'sqrt', + [ + 'coalesce', + [ + 'case', + ['==', ['feature-state', 'foobar'], null], + 100, + ['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 100], + ], + 100, + ], + ], + 10, + 32, + ]); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx new file mode 100644 index 0000000000000..d8fe8463edba8 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/dynamic_size_property.tsx @@ -0,0 +1,146 @@ +/* + * 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 React from 'react'; +import type { Map as MbMap } from '@kbn/mapbox-gl'; +import { DynamicStyleProperty } from '../dynamic_style_property'; +import { OrdinalLegend } from '../../components/legend/ordinal_legend'; +import { makeMbClampedNumberExpression } from '../../style_util'; +import { + FieldFormatter, + HALF_MAKI_ICON_SIZE, + VECTOR_STYLES, +} from '../../../../../../common/constants'; +import type { SizeDynamicOptions } from '../../../../../../common/descriptor_types'; +import type { IField } from '../../../../fields/field'; +import type { IVectorLayer } from '../../../../layers/vector_layer'; + +export class DynamicSizeProperty extends DynamicStyleProperty { + private readonly _isSymbolizedAsIcon: boolean; + + constructor( + options: SizeDynamicOptions, + styleName: VECTOR_STYLES, + field: IField | null, + vectorLayer: IVectorLayer, + getFieldFormatter: (fieldName: string) => null | FieldFormatter, + isSymbolizedAsIcon: boolean + ) { + super(options, styleName, field, vectorLayer, getFieldFormatter); + this._isSymbolizedAsIcon = isSymbolizedAsIcon; + } + + supportsFeatureState() { + // mb style "icon-size" does not support feature state + if (this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon) { + return false; + } + + // mb style "text-size" does not support feature state + if (this.getStyleName() === VECTOR_STYLES.LABEL_SIZE) { + return false; + } + + return true; + } + + syncHaloWidthWithMb(mbLayerId: string, mbMap: MbMap) { + mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', this.getMbSizeExpression()); + } + + syncIconSizeWithMb(symbolLayerId: string, mbMap: MbMap) { + mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this.getMbSizeExpression()); + } + + syncCircleStrokeWidthWithMb(mbLayerId: string, mbMap: MbMap) { + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', this.getMbSizeExpression()); + } + + syncCircleRadiusWithMb(mbLayerId: string, mbMap: MbMap) { + mbMap.setPaintProperty(mbLayerId, 'circle-radius', this.getMbSizeExpression()); + } + + syncLineWidthWithMb(mbLayerId: string, mbMap: MbMap) { + mbMap.setPaintProperty(mbLayerId, 'line-width', this.getMbSizeExpression()); + } + + syncLabelSizeWithMb(mbLayerId: string, mbMap: MbMap) { + mbMap.setLayoutProperty(mbLayerId, 'text-size', this.getMbSizeExpression()); + } + + /* + * Returns interpolation expression linearly translating domain values [minValue, maxValue] to display range [minSize, maxSize] + */ + getMbSizeExpression() { + const rangeFieldMeta = this.getRangeFieldMeta(); + if (!this._isSizeDynamicConfigComplete() || !rangeFieldMeta) { + // return min of size to avoid flashing + // returning minimum allows "growing" of the symbols when the meta comes in + // A grow effect us less visually jarring as shrinking. + // especially relevant when displaying fine-grained grids using mvt + return this._options.minSize >= 0 ? this._options.minSize : null; + } + + const isArea = this.getStyleName() === VECTOR_STYLES.ICON_SIZE; + // isArea === true + // It's a mistake to linearly map a data value to an area dimension (i.e. cirle radius). + // Area squares area dimension ("pie * r * r" or "x * x"), visually distorting proportions. + // Since it is the quadratic function that is causing this, + // we need to counteract its effects by applying its inverse function — the square-root function. + // https://bl.ocks.org/guilhermesimoes/e6356aa90a16163a6f917f53600a2b4a + + // can not take square root of 0 or negative number + // shift values to be positive integers >= 1 + const valueShift = rangeFieldMeta.min < 1 ? Math.abs(rangeFieldMeta.min) + 1 : 0; + + const maxValueStopInput = isArea + ? Math.sqrt(rangeFieldMeta.max + valueShift) + : rangeFieldMeta.max; + const minValueStopInput = isArea + ? Math.sqrt(rangeFieldMeta.min + valueShift) + : rangeFieldMeta.min; + const maxRangeStopOutput = + this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon + ? this._options.maxSize / HALF_MAKI_ICON_SIZE + : this._options.maxSize; + const minRangeStopOutput = + this.getStyleName() === VECTOR_STYLES.ICON_SIZE && this._isSymbolizedAsIcon + ? this._options.minSize / HALF_MAKI_ICON_SIZE + : this._options.minSize; + const stops = + rangeFieldMeta.min === rangeFieldMeta.max + ? [maxValueStopInput, maxRangeStopOutput] + : [minValueStopInput, minRangeStopOutput, maxValueStopInput, maxRangeStopOutput]; + + const valueExpression = makeMbClampedNumberExpression({ + lookupFunction: this.getMbLookupFunction(), + maxValue: rangeFieldMeta.max, + minValue: rangeFieldMeta.min, + fieldName: this.getMbFieldName(), + fallback: rangeFieldMeta.min, + }); + const valueShiftExpression = + rangeFieldMeta.min < 1 ? ['+', valueExpression, valueShift] : valueExpression; + const sqrtValueExpression = ['sqrt', valueShiftExpression]; + const inputExpression = isArea ? sqrtValueExpression : valueExpression; + + return ['interpolate', ['linear'], inputExpression, ...stops]; + } + + _isSizeDynamicConfigComplete() { + return ( + this._field && + this._field.isValid() && + this._options.minSize >= 0 && + this._options.maxSize >= 0 + ); + } + + renderLegendDetailRow() { + return ; + } +} diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/icon_size.test.ts similarity index 54% rename from x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx rename to x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/icon_size.test.ts index 0681d12007c4a..3144f4169ec87 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property.test.tsx +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/icon_size.test.ts @@ -5,57 +5,39 @@ * 2.0. */ -jest.mock('../components/vector_style_editor', () => ({ - VectorStyleEditor: () => { - return
mockVectorStyleEditor
; - }, -})); - -import React from 'react'; -import { shallow } from 'enzyme'; - import { DynamicSizeProperty } from './dynamic_size_property'; -import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../common/constants'; -import { IField } from '../../../fields/field'; -import type { Map as MbMap } from '@kbn/mapbox-gl'; -import { IVectorLayer } from '../../../layers/vector_layer'; - -export class MockMbMap { - _paintPropertyCalls: unknown[]; - - constructor() { - this._paintPropertyCalls = []; - } - setPaintProperty(...args: unknown[]) { - this._paintPropertyCalls.push([...args]); - } +import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../../common/constants'; +import { IField } from '../../../../fields/field'; +import { IVectorLayer } from '../../../../layers/vector_layer'; - getPaintPropertyCalls(): unknown[] { - return this._paintPropertyCalls; - } -} - -describe('renderLegendDetailRow', () => { - test('Should render as range', async () => { +describe('getMbSizeExpression - circle', () => { + test('Should return interpolation expression with square-root function', async () => { const field = { - getLabel: async () => { - return 'foobar_label'; + isValid: () => { + return true; }, getName: () => { return 'foodbar'; }, + getMbFieldName: () => { + return 'foobar'; + }, getOrigin: () => { return FIELD_ORIGIN.SOURCE; }, - supportsFieldMetaFromEs: () => { - return true; + getSource: () => { + return { + isMvt: () => { + return false; + }, + }; }, - supportsFieldMetaFromLocalData: () => { + supportsFieldMetaFromEs: () => { return true; }, } as unknown as IField; - const sizeProp = new DynamicSizeProperty( - { minSize: 0, maxSize: 10, fieldMetaOptions: { isEnabled: true } }, + const iconSize = new DynamicSizeProperty( + { minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } }, VECTOR_STYLES.ICON_SIZE, field, {} as unknown as IVectorLayer, @@ -64,7 +46,7 @@ describe('renderLegendDetailRow', () => { }, false ); - sizeProp.getRangeFieldMeta = () => { + iconSize.getRangeFieldMeta = () => { return { min: 0, max: 100, @@ -72,19 +54,34 @@ describe('renderLegendDetailRow', () => { }; }; - const legendRow = sizeProp.renderLegendDetailRow(); - const component = shallow(legendRow); - - // Ensure all promises resolve - await new Promise((resolve) => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); - expect(component).toMatchSnapshot(); + expect(iconSize.getMbSizeExpression()).toEqual([ + 'interpolate', + ['linear'], + [ + 'sqrt', + [ + '+', + [ + 'coalesce', + [ + 'case', + ['==', ['feature-state', 'foobar'], null], + 0, + ['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 0], + ], + 0, + ], + 1, + ], + ], + 1, + 8, + 10.04987562112089, + 32, + ]); }); -}); -describe('syncSize', () => { - test('Should sync with circle-radius prop', async () => { + test('Should return interpolation expression without value shift when range.min is > 1', async () => { const field = { isValid: () => { return true; @@ -109,7 +106,7 @@ describe('syncSize', () => { return true; }, } as unknown as IField; - const sizeProp = new DynamicSizeProperty( + const iconSize = new DynamicSizeProperty( { minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } }, VECTOR_STYLES.ICON_SIZE, field, @@ -119,51 +116,46 @@ describe('syncSize', () => { }, false ); - sizeProp.getRangeFieldMeta = () => { + iconSize.getRangeFieldMeta = () => { return { - min: 0, + min: 1, max: 100, delta: 100, }; }; - const mockMbMap = new MockMbMap() as unknown as MbMap; - sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap); - - // @ts-expect-error - expect(mockMbMap.getPaintPropertyCalls()).toEqual([ + expect(iconSize.getMbSizeExpression()).toEqual([ + 'interpolate', + ['linear'], [ - 'foobar', - 'circle-radius', + 'sqrt', [ - 'interpolate', - ['linear'], + 'coalesce', [ - 'coalesce', - [ - 'case', - ['==', ['feature-state', 'foobar'], null], - -1, - ['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 0], - ], - 0, + 'case', + ['==', ['feature-state', 'foobar'], null], + 1, + ['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 1], ], - 0, - 8, - 100, - 32, + 1, ], ], + 1, + 8, + 10, + 32, ]); }); +}); - test('Should truncate interpolate expression to max when no delta', async () => { +describe('getMbSizeExpression - icon', () => { + test('Should return interpolation expression with square-root function', async () => { const field = { isValid: () => { return true; }, getName: () => { - return 'foobar'; + return 'foodbar'; }, getMbFieldName: () => { return 'foobar'; @@ -182,7 +174,7 @@ describe('syncSize', () => { return true; }, } as unknown as IField; - const sizeProp = new DynamicSizeProperty( + const iconSize = new DynamicSizeProperty( { minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } }, VECTOR_STYLES.ICON_SIZE, field, @@ -190,41 +182,40 @@ describe('syncSize', () => { () => { return (value: RawValue) => value + '_format'; }, - false + true ); - sizeProp.getRangeFieldMeta = () => { + iconSize.getRangeFieldMeta = () => { return { - min: 100, + min: 0, max: 100, - delta: 0, + delta: 100, }; }; - const mockMbMap = new MockMbMap() as unknown as MbMap; - - sizeProp.syncCircleRadiusWithMb('foobar', mockMbMap); - // @ts-expect-error - expect(mockMbMap.getPaintPropertyCalls()).toEqual([ + expect(iconSize.getMbSizeExpression()).toEqual([ + 'interpolate', + ['linear'], [ - 'foobar', - 'circle-radius', + 'sqrt', [ - 'interpolate', - ['linear'], + '+', [ 'coalesce', [ 'case', - ['==', ['feature-state', 'foobar'], null], - 99, - ['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 100], + ['==', ['get', 'foobar'], null], + 0, + ['max', ['min', ['to-number', ['get', 'foobar']], 100], 0], ], 0, ], - 100, - 32, + 1, ], ], + 1, + 1, + 10.04987562112089, + 4, ]); }); }); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/index.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/index.ts new file mode 100644 index 0000000000000..bd324fc2da10f --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { DynamicSizeProperty } from './dynamic_size_property'; diff --git a/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/line_width.test.ts b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/line_width.test.ts new file mode 100644 index 0000000000000..f7453617d9609 --- /dev/null +++ b/x-pack/plugins/maps/public/classes/styles/vector/properties/dynamic_size_property/line_width.test.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 { DynamicSizeProperty } from './dynamic_size_property'; +import { FIELD_ORIGIN, RawValue, VECTOR_STYLES } from '../../../../../../common/constants'; +import { IField } from '../../../../fields/field'; +import { IVectorLayer } from '../../../../layers/vector_layer'; + +describe('getMbSizeExpression', () => { + test('Should return interpolation expression', async () => { + const field = { + isValid: () => { + return true; + }, + getName: () => { + return 'foodbar'; + }, + getMbFieldName: () => { + return 'foobar'; + }, + getOrigin: () => { + return FIELD_ORIGIN.SOURCE; + }, + getSource: () => { + return { + isMvt: () => { + return false; + }, + }; + }, + supportsFieldMetaFromEs: () => { + return true; + }, + } as unknown as IField; + const lineWidth = new DynamicSizeProperty( + { minSize: 8, maxSize: 32, fieldMetaOptions: { isEnabled: true } }, + VECTOR_STYLES.LINE_WIDTH, + field, + {} as unknown as IVectorLayer, + () => { + return (value: RawValue) => value + '_format'; + }, + false + ); + lineWidth.getRangeFieldMeta = () => { + return { + min: 0, + max: 100, + delta: 100, + }; + }; + + expect(lineWidth.getMbSizeExpression()).toEqual([ + 'interpolate', + ['linear'], + [ + 'coalesce', + [ + 'case', + ['==', ['feature-state', 'foobar'], null], + 0, + ['max', ['min', ['to-number', ['feature-state', 'foobar']], 100], 0], + ], + 0, + ], + 0, + 8, + 100, + 32, + ]); + }); +}); diff --git a/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts b/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts index ec2ecadf70fe3..5d4d5bc3ecbfb 100644 --- a/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts +++ b/x-pack/plugins/maps/public/classes/styles/vector/style_util.ts @@ -87,7 +87,7 @@ export function makeMbClampedNumberExpression({ [ 'case', ['==', [lookupFunction, fieldName], null], - minValue - 1, // == does a JS-y like check where returns true for null and undefined + fallback, // == does a JS-y like check where returns true for null and undefined clamp, ], fallback, diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts index e3612a9a125c9..e7d54fe29c4d7 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/alerts_details.spec.ts @@ -11,10 +11,13 @@ import { JSON_TEXT, TABLE_CONTAINER, TABLE_ROWS, + SUMMARY_VIEW_PREVALENCE_CELL, + SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON, } from '../../screens/alerts_details'; +import { QUERY_TAB_BUTTON, TIMELINE_TITLE } from '../../screens/timeline'; import { expandFirstAlert } from '../../tasks/alerts'; -import { openJsonView, openTable } from '../../tasks/alerts_details'; +import { openJsonView, openOverview, openTable } from '../../tasks/alerts_details'; import { createCustomRuleEnabled } from '../../tasks/api_calls/rules'; import { cleanKibana } from '../../tasks/common'; import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; @@ -80,4 +83,21 @@ describe('Alert details with unmapped fields', () => { expect($tableContainer[0].scrollLeft).to.equal(0); }); }); + + it('Opens a new timeline investigation', () => { + openOverview(); + + cy.get(SUMMARY_VIEW_PREVALENCE_CELL) + .invoke('text') + .then((alertCount) => { + // Click on the first button that lets us investigate in timeline + cy.get(ALERT_FLYOUT).find(SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON).click(); + + // Make sure a new timeline is created and opened + cy.get(TIMELINE_TITLE).should('contain.text', 'Untitled timeline'); + + // The alert count in this timeline should match the count shown on the alert flyout + cy.get(QUERY_TAB_BUTTON).should('contain.text', alertCount); + }); + }); }); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts index 95c0017d6a6ef..f035bbf2e3efd 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts_details.ts @@ -73,3 +73,9 @@ export const THREAT_INTEL_TAB = '[data-test-subj="threatIntelTab"]'; export const TITLE = '.euiTitle'; export const UPDATE_ENRICHMENT_RANGE_BUTTON = '[data-test-subj="enrichment-button"]'; + +export const OVERVIEW_TAB = '[data-test-subj="overviewTab"]'; + +export const SUMMARY_VIEW_PREVALENCE_CELL = `${SUMMARY_VIEW} [data-test-subj='alert-prevalence']`; + +export const SUMMARY_VIEW_INVESTIGATE_IN_TIMELINE_BUTTON = `${SUMMARY_VIEW} [aria-label='Investigate in timeline']`; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts index 091a483399ada..87aa83c3c10ec 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts @@ -9,6 +9,7 @@ import { ENRICHMENT_COUNT_NOTIFICATION, JSON_VIEW_WRAPPER, JSON_VIEW_TAB, + OVERVIEW_TAB, TABLE_TAB, } from '../screens/alerts_details'; @@ -16,6 +17,10 @@ export const openJsonView = () => { cy.get(JSON_VIEW_TAB).click(); }; +export const openOverview = () => { + cy.get(OVERVIEW_TAB).click(); +}; + export const openTable = () => { cy.get(TABLE_TAB).click(); }; diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx index 8df5bd3ce0194..69c881ef6eac8 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/alert_summary_view.tsx @@ -29,7 +29,9 @@ const AlertSummaryViewComponent: React.FC<{ [browserFields, data, eventId, isDraggable, timelineId, isReadOnly] ); - return ; + return ( + + ); }; export const AlertSummaryView = React.memo(AlertSummaryViewComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx index c2c88ec3e08c6..b000c3a6da703 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx @@ -159,6 +159,7 @@ const EventDetailsComponent: React.FC = ({ ? { id: EventsViewType.summaryView, name: i18n.OVERVIEW, + 'data-test-subj': 'overviewTab', content: ( <> diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx index 16fb1c4be8507..bf863576341c4 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.test.tsx @@ -86,10 +86,43 @@ describe('Summary View', () => { ); + // Shows the field name expect(screen.getByText(hostIpData.field)).toBeInTheDocument(); + // Shows all the field values hostIpValues.forEach((ipValue) => { expect(screen.getByText(ipValue)).toBeInTheDocument(); }); + + // Shows alert prevalence information + expect(screen.getByTestId('alert-prevalence')).toBeInTheDocument(); + // Shows the Investigate in timeline button + expect(screen.getByLabelText('Investigate in timeline')).toBeInTheDocument(); + }); + }); + + describe('when in readOnly mode', () => { + test('should only show the name and value cell', () => { + const sampleRows: AlertSummaryRow[] = [ + { + title: hostIpData.field, + description: enrichedHostIpData, + }, + ]; + + render( + + + + ); + + // Does not render the prevalence and timeline items + expect(screen.queryByTestId('alert-prevalence')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Investigate in timeline')).not.toBeInTheDocument(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx index 78cb55166555d..59215dcb5b1b6 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/summary_view.tsx @@ -23,8 +23,9 @@ import { VIEW_ALL_FIELDS } from './translations'; import { SummaryTable } from './table/summary_table'; import { SummaryValueCell } from './table/summary_value_cell'; import { PrevalenceCellRenderer } from './table/prevalence_cell'; +import { AddToTimelineCellRenderer } from './table/add_to_timeline_cell'; -const summaryColumns: Array> = [ +const baseColumns: Array> = [ { field: 'title', truncateText: false, @@ -37,6 +38,10 @@ const summaryColumns: Array> = [ render: SummaryValueCell, name: i18n.HIGHLIGHTED_FIELDS_VALUE, }, +]; + +const allColumns: Array> = [ + ...baseColumns, { field: 'description', truncateText: true, @@ -55,6 +60,13 @@ const summaryColumns: Array> = [ align: 'right', width: '130px', }, + { + field: 'description', + truncateText: true, + render: AddToTimelineCellRenderer, + name: '', + width: '30px', + }, ]; const rowProps = { @@ -66,7 +78,10 @@ const SummaryViewComponent: React.FC<{ goToTable: () => void; title: string; rows: AlertSummaryRow[]; -}> = ({ goToTable, rows, title }) => { + isReadOnly?: boolean; +}> = ({ goToTable, rows, title, isReadOnly }) => { + const columns = isReadOnly ? baseColumns : allColumns; + return (
@@ -85,7 +100,7 @@ const SummaryViewComponent: React.FC<{ diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx index 4749a0349e94d..9ec1d610912d9 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.test.tsx @@ -14,6 +14,8 @@ import { TestProviders } from '../../../mock'; import { EventFieldsData } from '../types'; import { TimelineId } from '../../../../../common/types'; +import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations'; + jest.mock('../../../lib/kibana'); const eventId = 'TUWyf3wBFCFU0qRJTauW'; @@ -58,7 +60,7 @@ describe('AddToTimelineCellRenderer', () => { /> ); - expect(screen.getByTestId('test-add-to-timeline')).toBeInTheDocument(); + expect(screen.queryByLabelText(ACTION_INVESTIGATE_IN_TIMELINE)).toBeInTheDocument(); }); }); @@ -76,7 +78,7 @@ describe('AddToTimelineCellRenderer', () => { /> ); - expect(screen.queryByTestId('test-add-to-timeline')).toBeNull(); + expect(screen.queryByLabelText(ACTION_INVESTIGATE_IN_TIMELINE)).not.toBeInTheDocument(); }); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx index fbf33e450ded3..6951611ecda8f 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/add_to_timeline_cell.tsx @@ -6,17 +6,23 @@ */ import React from 'react'; +import { EuiButtonIcon } from '@elastic/eui'; import { isEmpty } from 'lodash'; +import { useDispatch } from 'react-redux'; -import { useKibana } from '../../../lib/kibana'; import { AlertSummaryRow } from '../helpers'; +import { inputsActions } from '../../../store/inputs'; +import { updateProviders } from '../../../../timelines/store/timeline/actions'; +import { sourcererActions } from '../../../store/actions'; +import { SourcererScopeName } from '../../../store/sourcerer/model'; +import { TimelineId, TimelineType } from '../../../../../common/types/timeline'; import { useActionCellDataProvider } from './use_action_cell_data_provider'; +import { useCreateTimeline } from '../../../../timelines/components/timeline/properties/use_create_timeline'; +import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations'; const AddToTimelineCell = React.memo( ({ data, eventId, fieldFromBrowserField, linkValue, timelineId, values }) => { - const kibana = useKibana(); - const { timelines } = kibana.services; - const { getAddToTimelineButton } = timelines.getHoverActions(); + const dispatch = useDispatch(); const actionCellConfig = useActionCellDataProvider({ contextId: timelineId, @@ -30,14 +36,48 @@ const AddToTimelineCell = React.memo( values, }); + const clearTimeline = useCreateTimeline({ + timelineId: TimelineId.active, + timelineType: TimelineType.default, + }); + + const configureAndOpenTimeline = React.useCallback(() => { + if (actionCellConfig?.dataProvider) { + // Reset the current timeline + clearTimeline(); + // Update the timeline's providers to match the current prevalence field query + dispatch( + updateProviders({ + id: TimelineId.active, + providers: actionCellConfig.dataProvider, + }) + ); + // Only show detection alerts + // (This is required so the timeline event count matches the prevalence count) + dispatch( + sourcererActions.setSelectedDataView({ + id: SourcererScopeName.timeline, + selectedDataViewId: 'security-solution-default', + selectedPatterns: ['.alerts-security.alerts-default'], + }) + ); + // Unlock the time range from the global time range + dispatch(inputsActions.removeGlobalLinkTo()); + } + }, [dispatch, clearTimeline, actionCellConfig]); + const showButton = values != null && !isEmpty(actionCellConfig?.dataProvider); if (showButton) { - return getAddToTimelineButton({ - dataProvider: actionCellConfig?.dataProvider, - field: data.field, - ownFocus: true, - }); + return ( + + ); } else { return null; } diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx index 2a11736f52c42..71925a72dca83 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/table/prevalence_cell.tsx @@ -24,7 +24,7 @@ const PrevalenceCell = React.memo( if (loading) { return ; } else { - return defaultToEmptyTag(count); + return {defaultToEmptyTag(count)}; } } ); diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_pagination.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_pagination.tsx index a735c430325f7..a3f682473eba1 100644 --- a/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_pagination.tsx +++ b/x-pack/plugins/security_solution/public/common/components/exceptions/viewer/exceptions_pagination.tsx @@ -53,8 +53,8 @@ const ExceptionsViewerPaginationComponent = ({ const items = useMemo((): ReactElement[] => { return pagination.pageSizeOptions.map((rows) => ( { onPaginationChange({ pagination: { diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 513f83e23f177..e1cb49cbcbc05 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1936,7 +1936,6 @@ "data.aggTypes.buckets.ranges.rangesFormatMessageArrowRight": "{from} → {to}", "data.errors.fetchError": "Vérifiez votre réseau et la configuration de votre proxy. Si le problème persiste, contactez votre administrateur réseau.", "data.esError.unknownRootCause": "inconnue", - "data.filter.filterBar.fieldNotFound": "Champ {key} introuvable dans le modèle d'indexation {indexPattern}", "data.functions.esaggs.help": "Exécuter l'agrégation AggConfig", "data.functions.esaggs.inspector.dataRequest.description": "Cette requête interroge Elasticsearch pour récupérer les données pour la visualisation.", "data.functions.esaggs.inspector.dataRequest.title": "Données", @@ -31023,7 +31022,6 @@ "unifiedSearch.filter.filterEditor.falseOptionLabel": "false", "unifiedSearch.filter.filterEditor.fieldSelectLabel": "Champ", "unifiedSearch.filter.filterEditor.fieldSelectPlaceholder": "Sélectionner d'abord un champ", - "unifiedSearch.filter.filterEditor.indexPatternSelectLabel": "Modèle d'indexation", "unifiedSearch.filter.filterEditor.isBetweenOperatorOptionLabel": "est entre", "unifiedSearch.filter.filterEditor.isNotBetweenOperatorOptionLabel": "n'est pas entre", "unifiedSearch.filter.filterEditor.isNotOneOfOperatorOptionLabel": "n'est pas l'une des options suivantes", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index df42895ac2cbc..56e5661cbd629 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2030,7 +2030,6 @@ "data.aggTypes.buckets.ranges.rangesFormatMessageArrowRight": "{from} → {to}", "data.errors.fetchError": "ネットワークとプロキシ構成を確認してください。問題が解決しない場合は、ネットワーク管理者に問い合わせてください。", "data.esError.unknownRootCause": "不明", - "data.filter.filterBar.fieldNotFound": "インデックスパターン {indexPattern} にフィールド {key} がありません", "data.functions.esaggs.help": "AggConfig 集約を実行します", "data.functions.esaggs.inspector.dataRequest.description": "このリクエストはElasticsearchにクエリし、ビジュアライゼーション用のデータを取得します。", "data.functions.esaggs.inspector.dataRequest.title": "データ", @@ -31233,7 +31232,6 @@ "unifiedSearch.filter.filterEditor.falseOptionLabel": "false", "unifiedSearch.filter.filterEditor.fieldSelectLabel": "フィールド", "unifiedSearch.filter.filterEditor.fieldSelectPlaceholder": "フィールドを選択", - "unifiedSearch.filter.filterEditor.indexPatternSelectLabel": "インデックスパターン", "unifiedSearch.filter.filterEditor.isBetweenOperatorOptionLabel": "is between", "unifiedSearch.filter.filterEditor.isNotBetweenOperatorOptionLabel": "is not between", "unifiedSearch.filter.filterEditor.isNotOneOfOperatorOptionLabel": "is not one of", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 6783295921119..cbcbad2131bd0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2038,7 +2038,6 @@ "data.aggTypes.buckets.ranges.rangesFormatMessageArrowRight": "{from} → {to}", "data.errors.fetchError": "请检查您的网络和代理配置。如果问题持续存在,请联系网络管理员。", "data.esError.unknownRootCause": "未知", - "data.filter.filterBar.fieldNotFound": "索引模式 {indexPattern} 中未找到字段 {key}", "data.functions.esaggs.help": "运行 AggConfig 聚合", "data.functions.esaggs.inspector.dataRequest.description": "此请求查询 Elasticsearch,以获取可视化的数据。", "data.functions.esaggs.inspector.dataRequest.title": "数据", @@ -31269,7 +31268,6 @@ "unifiedSearch.filter.filterEditor.falseOptionLabel": "false", "unifiedSearch.filter.filterEditor.fieldSelectLabel": "字段", "unifiedSearch.filter.filterEditor.fieldSelectPlaceholder": "首先选择字段", - "unifiedSearch.filter.filterEditor.indexPatternSelectLabel": "索引模式", "unifiedSearch.filter.filterEditor.isBetweenOperatorOptionLabel": "介于", "unifiedSearch.filter.filterEditor.isNotBetweenOperatorOptionLabel": "不介于", "unifiedSearch.filter.filterEditor.isNotOneOfOperatorOptionLabel": "不属于", diff --git a/x-pack/test/functional/apps/dashboard/group1/drilldowns/dashboard_to_dashboard_drilldown.ts b/x-pack/test/functional/apps/dashboard/group1/drilldowns/dashboard_to_dashboard_drilldown.ts index c540d5673d89e..1040498a9bd05 100644 --- a/x-pack/test/functional/apps/dashboard/group1/drilldowns/dashboard_to_dashboard_drilldown.ts +++ b/x-pack/test/functional/apps/dashboard/group1/drilldowns/dashboard_to_dashboard_drilldown.ts @@ -41,6 +41,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await security.testUser.setRoles(['test_logstash_reader', 'global_dashboard_all']); await PageObjects.common.navigateToApp('dashboard'); await PageObjects.dashboard.preserveCrossAppState(); + await elasticChart.setNewChartUiDebugFlag(); }); after(async () => { @@ -141,7 +142,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testDashboardDrilldown = async (drilldownAction: (text: string) => Promise) => { // trigger drilldown action by clicking on a pie and picking drilldown action by it's name - await pieChart.clickOnPieSlice('40,000'); + await pieChart.clickOnPieSlice('40000'); await dashboardDrilldownPanelActions.expectMultipleActionsMenuOpened(); const href = await dashboardDrilldownPanelActions.getActionHrefByText( diff --git a/x-pack/test/functional/apps/dashboard/group1/reporting/reports/baseline/large_dashboard_preserve_layout.png b/x-pack/test/functional/apps/dashboard/group1/reporting/reports/baseline/large_dashboard_preserve_layout.png index 0694d85f4f5ff..776570da9ce07 100644 Binary files a/x-pack/test/functional/apps/dashboard/group1/reporting/reports/baseline/large_dashboard_preserve_layout.png and b/x-pack/test/functional/apps/dashboard/group1/reporting/reports/baseline/large_dashboard_preserve_layout.png differ diff --git a/x-pack/test/functional/apps/dashboard/group1/reporting/reports/baseline/small_dashboard_preserve_layout.png b/x-pack/test/functional/apps/dashboard/group1/reporting/reports/baseline/small_dashboard_preserve_layout.png index 13a68e97b3286..aa763e85bb615 100644 Binary files a/x-pack/test/functional/apps/dashboard/group1/reporting/reports/baseline/small_dashboard_preserve_layout.png and b/x-pack/test/functional/apps/dashboard/group1/reporting/reports/baseline/small_dashboard_preserve_layout.png differ diff --git a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts index 20fe8a9413723..658581d1297f7 100644 --- a/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts +++ b/x-pack/test/functional/apps/dashboard/group2/migration_smoke_tests/controls_migration_smoke_test.ts @@ -17,9 +17,11 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const pieChart = getService('pieChart'); + const elasticChart = getService('elasticChart'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); + const queryBar = getService('queryBar'); const { common, settings, savedObjects, dashboard, dashboardControls } = getPageObjects([ 'common', @@ -95,7 +97,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('applies default selected options list options to dashboard', async () => { // because 4 selections are made on the control, the pie chart should only show 4 slices. - expect(await pieChart.getPieSliceCount()).to.be(4); + await elasticChart.setNewChartUiDebugFlag(); + await queryBar.submitQuery(); + await pieChart.expectPieSliceCountEsCharts(4); }); }); } diff --git a/x-pack/test/functional/config.base.js b/x-pack/test/functional/config.base.js index a4d73d40e2d4d..e422b0ca67657 100644 --- a/x-pack/test/functional/config.base.js +++ b/x-pack/test/functional/config.base.js @@ -58,7 +58,6 @@ export default async function ({ readConfigFile }) { defaults: { 'accessibility:disableAnimations': true, 'dateFormat:tz': 'UTC', - 'visualization:visualize:legacyPieChartsLibrary': true, }, }, // the apps section defines the urls that diff --git a/x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json b/x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json index deb14fe74b187..b730c10fd7628 100644 --- a/x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json +++ b/x-pack/test/functional/fixtures/kbn_archiver/rollup/rollup.json @@ -3,8 +3,7 @@ "accessibility:disableAnimations": true, "buildNum": 9007199254740991, "dateFormat:tz": "UTC", - "defaultIndex": "rollup", - "visualization:visualize:legacyPieChartsLibrary": true + "defaultIndex": "rollup" }, "coreMigrationVersion": "7.15.0", "id": "7.15.0", diff --git a/x-pack/test/functional_synthetics/config.js b/x-pack/test/functional_synthetics/config.js index f5db09a5d60d9..cf529226de895 100644 --- a/x-pack/test/functional_synthetics/config.js +++ b/x-pack/test/functional_synthetics/config.js @@ -75,7 +75,6 @@ export default async function ({ readConfigFile }) { defaults: { 'accessibility:disableAnimations': true, 'dateFormat:tz': 'UTC', - 'visualization:visualize:legacyPieChartsLibrary': true, }, }, // the apps section defines the urls that diff --git a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts index b87ec4dfe4159..9f1b14040c6a6 100644 --- a/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts +++ b/x-pack/test/upgrade/apps/dashboard/dashboard_smoke_tests.ts @@ -15,7 +15,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const renderable = getService('renderable'); const dashboardExpect = getService('dashboardExpect'); const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'timePicker']); - const kibanaServer = getService('kibanaServer'); const browser = getService('browser'); describe('upgrade dashboard smoke tests', function describeIndexTests() { @@ -37,12 +36,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { basePath, }); await PageObjects.header.waitUntilLoadingHasFinished(); - await kibanaServer.uiSettings.update( - { - 'visualization:visualize:legacyPieChartsLibrary': true, - }, - { space } - ); await browser.refresh(); }); dashboardTests.forEach(({ name, numPanels }) => {