From ce693b01963d2178dd47279b0de689f8bdae222c Mon Sep 17 00:00:00 2001
From: Nathan L Smith
Date: Wed, 24 Jun 2020 09:01:13 -0500
Subject: [PATCH 01/82] [APM] Storybook theme fixes (#69730) (#69794)
* [APM] Storybook theme fixes
The changes adding theme support in #69362 broke some of the Storybook stories.
Add decorators to wrap some of the stories in the theme context.
This should be done in a global decorator, but our current storybook setup doesn't support this. It also would be nice to be able to switch between light/dark mode, but that's something we can add in the future.
* Remove unused import
* Adds missing decorator to cytoscape examples + adds a new real-world example
Co-authored-by: Oliver Gupte
Co-authored-by: Elastic Machine
Co-authored-by: Oliver Gupte
Co-authored-by: Elastic Machine
---
.../__stories__/Cytoscape.stories.tsx | 107 +-
.../CytoscapeExampleData.stories.tsx | 434 ++--
.../example_response_one_domain_many_ips.json | 2122 +++++++++++++++++
.../index.stories.tsx | 38 +-
4 files changed, 2434 insertions(+), 267 deletions(-)
create mode 100644 x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/example_response_one_domain_many_ips.json
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
index 28cb7a6f9d291..aee392b53298a 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/Cytoscape.stories.tsx
@@ -10,60 +10,63 @@ import cytoscape from 'cytoscape';
import React from 'react';
import { Cytoscape } from '../Cytoscape';
import { iconForNode } from '../icons';
+import { EuiThemeProvider } from '../../../../../../observability/public';
-storiesOf('app/ServiceMap/Cytoscape', module).add(
- 'example',
- () => {
- const elements: cytoscape.ElementDefinition[] = [
- {
- data: {
- id: 'opbeans-python',
- 'service.name': 'opbeans-python',
- 'agent.name': 'python',
- },
- },
- {
- data: {
- id: 'opbeans-node',
- 'service.name': 'opbeans-node',
- 'agent.name': 'nodejs',
- },
- },
- {
- data: {
- id: 'opbeans-ruby',
- 'service.name': 'opbeans-ruby',
- 'agent.name': 'ruby',
- },
- },
- { data: { source: 'opbeans-python', target: 'opbeans-node' } },
- {
- data: {
- bidirectional: true,
- source: 'opbeans-python',
- target: 'opbeans-ruby',
- },
- },
- ];
- const height = 300;
- const width = 1340;
- const serviceName = 'opbeans-python';
- return (
-
- );
- },
- {
- info: {
- propTables: false,
- source: false,
+storiesOf('app/ServiceMap/Cytoscape', module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'example',
+ () => {
+ const elements: cytoscape.ElementDefinition[] = [
+ {
+ data: {
+ id: 'opbeans-python',
+ 'service.name': 'opbeans-python',
+ 'agent.name': 'python',
+ },
+ },
+ {
+ data: {
+ id: 'opbeans-node',
+ 'service.name': 'opbeans-node',
+ 'agent.name': 'nodejs',
+ },
+ },
+ {
+ data: {
+ id: 'opbeans-ruby',
+ 'service.name': 'opbeans-ruby',
+ 'agent.name': 'ruby',
+ },
+ },
+ { data: { source: 'opbeans-python', target: 'opbeans-node' } },
+ {
+ data: {
+ bidirectional: true,
+ source: 'opbeans-python',
+ target: 'opbeans-ruby',
+ },
+ },
+ ];
+ const height = 300;
+ const width = 1340;
+ const serviceName = 'opbeans-python';
+ return (
+
+ );
},
- }
-);
+ {
+ info: {
+ propTables: false,
+ source: false,
+ },
+ }
+ );
storiesOf('app/ServiceMap/Cytoscape', module).add(
'node icons',
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx
index 3aced1b33dcac..44278b2846128 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/CytoscapeExampleData.stories.tsx
@@ -6,23 +6,25 @@
/* eslint-disable no-console */
import {
+ EuiButton,
+ EuiCodeEditor,
+ EuiFieldNumber,
+ EuiFilePicker,
EuiFlexGroup,
EuiFlexItem,
- EuiButton,
EuiForm,
- EuiFieldNumber,
- EuiToolTip,
- EuiCodeEditor,
EuiSpacer,
- EuiFilePicker,
+ EuiToolTip,
} from '@elastic/eui';
import { storiesOf } from '@storybook/react';
-import React, { useState, useEffect } from 'react';
+import React, { useEffect, useState } from 'react';
+import { EuiThemeProvider } from '../../../../../../observability/public';
import { Cytoscape } from '../Cytoscape';
-import { generateServiceMapElements } from './generate_service_map_elements';
-import exampleResponseOpbeansBeats from './example_response_opbeans_beats.json';
import exampleResponseHipsterStore from './example_response_hipster_store.json';
+import exampleResponseOpbeansBeats from './example_response_opbeans_beats.json';
import exampleResponseTodo from './example_response_todo.json';
+import exampleResponseOneDomainManyIPs from './example_response_one_domain_many_ips.json';
+import { generateServiceMapElements } from './generate_service_map_elements';
const STORYBOOK_PATH = 'app/ServiceMap/Cytoscape/Example data';
@@ -34,151 +36,155 @@ function setSessionJson(json: string) {
window.sessionStorage.setItem(SESSION_STORAGE_KEY, json);
}
-storiesOf(STORYBOOK_PATH, module).add(
- 'Generate map',
- () => {
- const [size, setSize] = useState(10);
- const [json, setJson] = useState('');
- const [elements, setElements] = useState(
- generateServiceMapElements(size)
- );
-
- return (
-
-
-
- {
- setElements(generateServiceMapElements(size));
- setJson('');
- }}
- >
- Generate service map
-
-
-
-
- setSize(e.target.valueAsNumber)}
- />
-
-
-
- {
- setJson(JSON.stringify({ elements }, null, 2));
- }}
- >
- Get JSON
-
-
-
-
-
-
- {json && (
-
- )}
-
- );
- },
- {
- info: { propTables: false, source: false },
- }
-);
-
-storiesOf(STORYBOOK_PATH, module).add(
- 'Map from JSON',
- () => {
- const [json, setJson] = useState(
- getSessionJson() || JSON.stringify(exampleResponseTodo, null, 2)
- );
- const [error, setError] = useState();
-
- const [elements, setElements] = useState([]);
- useEffect(() => {
- try {
- setElements(JSON.parse(json).elements);
- } catch (e) {
- setError(e.message);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- return (
-
-
-
+storiesOf(STORYBOOK_PATH, module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'Generate map',
+ () => {
+ const [size, setSize] = useState(10);
+ const [json, setJson] = useState('');
+ const [elements, setElements] = useState(
+ generateServiceMapElements(size)
+ );
+
+ return (
+
- {
- setJson(value);
+ {
+ setElements(generateServiceMapElements(size));
+ setJson('');
}}
- />
+ >
+ Generate service map
+
-
- {
- const item = event?.item(0);
-
- if (item) {
- const f = new FileReader();
- f.onload = (onloadEvent) => {
- const result = onloadEvent?.target?.result;
- if (typeof result === 'string') {
- setJson(result);
- }
- };
- f.readAsText(item);
- }
- }}
+
+ setSize(e.target.valueAsNumber)}
/>
-
- {
- try {
- setElements(JSON.parse(json).elements);
- setSessionJson(json);
- setError(undefined);
- } catch (e) {
- setError(e.message);
- }
- }}
- >
- Render JSON
-
-
+
+
+
+ {
+ setJson(JSON.stringify({ elements }, null, 2));
+ }}
+ >
+ Get JSON
+
-
-
- );
- },
- {
- info: {
- propTables: false,
- source: false,
- text: `
+
+
+
+ {json && (
+
+ )}
+
+ );
+ },
+ {
+ info: { propTables: false, source: false },
+ }
+ );
+
+storiesOf(STORYBOOK_PATH, module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'Map from JSON',
+ () => {
+ const [json, setJson] = useState(
+ getSessionJson() || JSON.stringify(exampleResponseTodo, null, 2)
+ );
+ const [error, setError] = useState();
+
+ const [elements, setElements] = useState([]);
+ useEffect(() => {
+ try {
+ setElements(JSON.parse(json).elements);
+ } catch (e) {
+ setError(e.message);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+
+
+
+
+
+ {
+ setJson(value);
+ }}
+ />
+
+
+
+ {
+ const item = event?.item(0);
+
+ if (item) {
+ const f = new FileReader();
+ f.onload = (onloadEvent) => {
+ const result = onloadEvent?.target?.result;
+ if (typeof result === 'string') {
+ setJson(result);
+ }
+ };
+ f.readAsText(item);
+ }
+ }}
+ />
+
+ {
+ try {
+ setElements(JSON.parse(json).elements);
+ setSessionJson(json);
+ setError(undefined);
+ } catch (e) {
+ setError(e.message);
+ }
+ }}
+ >
+ Render JSON
+
+
+
+
+
+
+ );
+ },
+ {
+ info: {
+ propTables: false,
+ source: false,
+ text: `
Enter JSON map data into the text box or upload a file and click "Render JSON" to see the results. You can enable a download button on the service map by putting
\`\`\`
@@ -186,60 +192,86 @@ storiesOf(STORYBOOK_PATH, module).add(
\`\`\`
into the JavaScript console and reloading the page.`,
+ },
+ }
+ );
+
+storiesOf(STORYBOOK_PATH, module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'Todo app',
+ () => {
+ return (
+
+
+
+ );
+ },
+ {
+ info: { propTables: false, source: false },
+ }
+ );
+
+storiesOf(STORYBOOK_PATH, module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'Opbeans + beats',
+ () => {
+ return (
+
+
+
+ );
+ },
+ {
+ info: { propTables: false, source: false },
+ }
+ );
+
+storiesOf(STORYBOOK_PATH, module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'Hipster store',
+ () => {
+ return (
+
+
+
+ );
+ },
+ {
+ info: { propTables: false, source: false },
+ }
+ );
+
+storiesOf(STORYBOOK_PATH, module)
+ .addDecorator((storyFn) => {storyFn()} )
+ .add(
+ 'Node resolves one domain name to many IPs',
+ () => {
+ return (
+
+
+
+ );
},
- }
-);
-
-storiesOf(STORYBOOK_PATH, module).add(
- 'Todo app',
- () => {
- return (
-
-
-
- );
- },
- {
- info: { propTables: false, source: false },
- }
-);
-
-storiesOf(STORYBOOK_PATH, module).add(
- 'Opbeans + beats',
- () => {
- return (
-
-
-
- );
- },
- {
- info: { propTables: false, source: false },
- }
-);
-
-storiesOf(STORYBOOK_PATH, module).add(
- 'Hipster store',
- () => {
- return (
-
-
-
- );
- },
- {
- info: { propTables: false, source: false },
- }
-);
+ {
+ info: { propTables: false, source: false },
+ }
+ );
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/example_response_one_domain_many_ips.json b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/example_response_one_domain_many_ips.json
new file mode 100644
index 0000000000000..f9b8a273d8577
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/__stories__/example_response_one_domain_many_ips.json
@@ -0,0 +1,2122 @@
+{
+ "elements": [
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.99:80",
+ "id": "artifact_api~>192.0.2.99:80",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "http",
+ "span.destination.service.resource": "192.0.2.99:80",
+ "span.type": "external",
+ "id": ">192.0.2.99:80",
+ "label": ">192.0.2.99:80"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.47:443",
+ "id": "artifact_api~>192.0.2.47:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.47:443",
+ "span.type": "external",
+ "id": ">192.0.2.47:443",
+ "label": ">192.0.2.47:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.13:443",
+ "id": "artifact_api~>192.0.2.13:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.13:443",
+ "span.type": "external",
+ "id": ">192.0.2.13:443",
+ "label": ">192.0.2.13:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.106:443",
+ "id": "artifact_api~>192.0.2.106:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.106:443",
+ "span.type": "external",
+ "id": ">192.0.2.106:443",
+ "label": ">192.0.2.106:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.83:443",
+ "id": "artifact_api~>192.0.2.83:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.83:443",
+ "span.type": "external",
+ "id": ">192.0.2.83:443",
+ "label": ">192.0.2.83:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.111:443",
+ "id": "artifact_api~>192.0.2.111:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.111:443",
+ "span.type": "external",
+ "id": ">192.0.2.111:443",
+ "label": ">192.0.2.111:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.189:443",
+ "id": "artifact_api~>192.0.2.189:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.189:443",
+ "span.type": "external",
+ "id": ">192.0.2.189:443",
+ "label": ">192.0.2.189:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.148:443",
+ "id": "artifact_api~>192.0.2.148:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.148:443",
+ "span.type": "external",
+ "id": ">192.0.2.148:443",
+ "label": ">192.0.2.148:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.39:443",
+ "id": "artifact_api~>192.0.2.39:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.39:443",
+ "span.type": "external",
+ "id": ">192.0.2.39:443",
+ "label": ">192.0.2.39:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.42:443",
+ "id": "artifact_api~>192.0.2.42:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.42:443",
+ "span.type": "external",
+ "id": ">192.0.2.42:443",
+ "label": ">192.0.2.42:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.240:443",
+ "id": "artifact_api~>192.0.2.240:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.240:443",
+ "span.type": "external",
+ "id": ">192.0.2.240:443",
+ "label": ">192.0.2.240:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.156:443",
+ "id": "artifact_api~>192.0.2.156:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.156:443",
+ "span.type": "external",
+ "id": ">192.0.2.156:443",
+ "label": ">192.0.2.156:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.245:443",
+ "id": "artifact_api~>192.0.2.245:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.245:443",
+ "span.type": "external",
+ "id": ">192.0.2.245:443",
+ "label": ">192.0.2.245:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.198:443",
+ "id": "artifact_api~>192.0.2.198:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.198:443",
+ "span.type": "external",
+ "id": ">192.0.2.198:443",
+ "label": ">192.0.2.198:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.77:443",
+ "id": "artifact_api~>192.0.2.77:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.77:443",
+ "span.type": "external",
+ "id": ">192.0.2.77:443",
+ "label": ">192.0.2.77:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.8:443",
+ "id": "artifact_api~>192.0.2.8:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.8:443",
+ "span.type": "external",
+ "id": ">192.0.2.8:443",
+ "label": ">192.0.2.8:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.69:443",
+ "id": "artifact_api~>192.0.2.69:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.69:443",
+ "span.type": "external",
+ "id": ">192.0.2.69:443",
+ "label": ">192.0.2.69:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.5:443",
+ "id": "artifact_api~>192.0.2.5:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.5:443",
+ "span.type": "external",
+ "id": ">192.0.2.5:443",
+ "label": ">192.0.2.5:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.139:443",
+ "id": "artifact_api~>192.0.2.139:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.139:443",
+ "span.type": "external",
+ "id": ">192.0.2.139:443",
+ "label": ">192.0.2.139:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.113:443",
+ "id": "artifact_api~>192.0.2.113:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.113:443",
+ "span.type": "external",
+ "id": ">192.0.2.113:443",
+ "label": ">192.0.2.113:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.2:443",
+ "id": "artifact_api~>192.0.2.2:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.2:443",
+ "span.type": "external",
+ "id": ">192.0.2.2:443",
+ "label": ">192.0.2.2:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.213:443",
+ "id": "artifact_api~>192.0.2.213:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.213:443",
+ "span.type": "external",
+ "id": ">192.0.2.213:443",
+ "label": ">192.0.2.213:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.153:443",
+ "id": "artifact_api~>192.0.2.153:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.153:443",
+ "span.type": "external",
+ "id": ">192.0.2.153:443",
+ "label": ">192.0.2.153:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.36:443",
+ "id": "artifact_api~>192.0.2.36:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.36:443",
+ "span.type": "external",
+ "id": ">192.0.2.36:443",
+ "label": ">192.0.2.36:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.164:443",
+ "id": "artifact_api~>192.0.2.164:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.164:443",
+ "span.type": "external",
+ "id": ">192.0.2.164:443",
+ "label": ">192.0.2.164:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.190:443",
+ "id": "artifact_api~>192.0.2.190:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.190:443",
+ "span.type": "external",
+ "id": ">192.0.2.190:443",
+ "label": ">192.0.2.190:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.9:443",
+ "id": "artifact_api~>192.0.2.9:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.9:443",
+ "span.type": "external",
+ "id": ">192.0.2.9:443",
+ "label": ">192.0.2.9:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.210:443",
+ "id": "artifact_api~>192.0.2.210:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.210:443",
+ "span.type": "external",
+ "id": ">192.0.2.210:443",
+ "label": ">192.0.2.210:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.21:443",
+ "id": "artifact_api~>192.0.2.21:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.21:443",
+ "span.type": "external",
+ "id": ">192.0.2.21:443",
+ "label": ">192.0.2.21:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.176:443",
+ "id": "artifact_api~>192.0.2.176:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.176:443",
+ "span.type": "external",
+ "id": ">192.0.2.176:443",
+ "label": ">192.0.2.176:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.81:443",
+ "id": "artifact_api~>192.0.2.81:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.81:443",
+ "span.type": "external",
+ "id": ">192.0.2.81:443",
+ "label": ">192.0.2.81:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.118:443",
+ "id": "artifact_api~>192.0.2.118:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.118:443",
+ "span.type": "external",
+ "id": ">192.0.2.118:443",
+ "label": ">192.0.2.118:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.103:443",
+ "id": "artifact_api~>192.0.2.103:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.103:443",
+ "span.type": "external",
+ "id": ">192.0.2.103:443",
+ "label": ">192.0.2.103:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.3:443",
+ "id": "artifact_api~>192.0.2.3:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.3:443",
+ "span.type": "external",
+ "id": ">192.0.2.3:443",
+ "label": ">192.0.2.3:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.135:443",
+ "id": "artifact_api~>192.0.2.135:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.135:443",
+ "span.type": "external",
+ "id": ">192.0.2.135:443",
+ "label": ">192.0.2.135:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.26:443",
+ "id": "artifact_api~>192.0.2.26:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.26:443",
+ "span.type": "external",
+ "id": ">192.0.2.26:443",
+ "label": ">192.0.2.26:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.185:443",
+ "id": "artifact_api~>192.0.2.185:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.185:443",
+ "span.type": "external",
+ "id": ">192.0.2.185:443",
+ "label": ">192.0.2.185:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.173:443",
+ "id": "artifact_api~>192.0.2.173:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.173:443",
+ "span.type": "external",
+ "id": ">192.0.2.173:443",
+ "label": ">192.0.2.173:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.45:443",
+ "id": "artifact_api~>192.0.2.45:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.45:443",
+ "span.type": "external",
+ "id": ">192.0.2.45:443",
+ "label": ">192.0.2.45:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.144:443",
+ "id": "artifact_api~>192.0.2.144:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.144:443",
+ "span.type": "external",
+ "id": ">192.0.2.144:443",
+ "label": ">192.0.2.144:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.165:443",
+ "id": "artifact_api~>192.0.2.165:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.165:443",
+ "span.type": "external",
+ "id": ">192.0.2.165:443",
+ "label": ">192.0.2.165:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.119:443",
+ "id": "artifact_api~>192.0.2.119:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.119:443",
+ "span.type": "external",
+ "id": ">192.0.2.119:443",
+ "label": ">192.0.2.119:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.186:443",
+ "id": "artifact_api~>192.0.2.186:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.186:443",
+ "span.type": "external",
+ "id": ">192.0.2.186:443",
+ "label": ">192.0.2.186:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.54:443",
+ "id": "artifact_api~>192.0.2.54:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.54:443",
+ "span.type": "external",
+ "id": ">192.0.2.54:443",
+ "label": ">192.0.2.54:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.23:443",
+ "id": "artifact_api~>192.0.2.23:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.23:443",
+ "span.type": "external",
+ "id": ">192.0.2.23:443",
+ "label": ">192.0.2.23:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.34:443",
+ "id": "artifact_api~>192.0.2.34:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.34:443",
+ "span.type": "external",
+ "id": ">192.0.2.34:443",
+ "label": ">192.0.2.34:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.169:443",
+ "id": "artifact_api~>192.0.2.169:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.169:443",
+ "span.type": "external",
+ "id": ">192.0.2.169:443",
+ "label": ">192.0.2.169:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.226:443",
+ "id": "artifact_api~>192.0.2.226:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.226:443",
+ "span.type": "external",
+ "id": ">192.0.2.226:443",
+ "label": ">192.0.2.226:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.82:443",
+ "id": "artifact_api~>192.0.2.82:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.82:443",
+ "span.type": "external",
+ "id": ">192.0.2.82:443",
+ "label": ">192.0.2.82:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.132:443",
+ "id": "artifact_api~>192.0.2.132:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.132:443",
+ "span.type": "external",
+ "id": ">192.0.2.132:443",
+ "label": ">192.0.2.132:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.78:443",
+ "id": "artifact_api~>192.0.2.78:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.78:443",
+ "span.type": "external",
+ "id": ">192.0.2.78:443",
+ "label": ">192.0.2.78:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.71:443",
+ "id": "artifact_api~>192.0.2.71:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.71:443",
+ "span.type": "external",
+ "id": ">192.0.2.71:443",
+ "label": ">192.0.2.71:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.48:443",
+ "id": "artifact_api~>192.0.2.48:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.48:443",
+ "span.type": "external",
+ "id": ">192.0.2.48:443",
+ "label": ">192.0.2.48:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.107:443",
+ "id": "artifact_api~>192.0.2.107:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.107:443",
+ "span.type": "external",
+ "id": ">192.0.2.107:443",
+ "label": ">192.0.2.107:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.239:443",
+ "id": "artifact_api~>192.0.2.239:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.239:443",
+ "span.type": "external",
+ "id": ">192.0.2.239:443",
+ "label": ">192.0.2.239:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.209:443",
+ "id": "artifact_api~>192.0.2.209:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.209:443",
+ "span.type": "external",
+ "id": ">192.0.2.209:443",
+ "label": ">192.0.2.209:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.248:443",
+ "id": "artifact_api~>192.0.2.248:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.248:443",
+ "span.type": "external",
+ "id": ">192.0.2.248:443",
+ "label": ">192.0.2.248:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.18:443",
+ "id": "artifact_api~>192.0.2.18:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.18:443",
+ "span.type": "external",
+ "id": ">192.0.2.18:443",
+ "label": ">192.0.2.18:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.228:443",
+ "id": "artifact_api~>192.0.2.228:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.228:443",
+ "span.type": "external",
+ "id": ">192.0.2.228:443",
+ "label": ">192.0.2.228:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.145:443",
+ "id": "artifact_api~>192.0.2.145:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.145:443",
+ "span.type": "external",
+ "id": ">192.0.2.145:443",
+ "label": ">192.0.2.145:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.25:443",
+ "id": "artifact_api~>192.0.2.25:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.25:443",
+ "span.type": "external",
+ "id": ">192.0.2.25:443",
+ "label": ">192.0.2.25:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.162:443",
+ "id": "artifact_api~>192.0.2.162:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.162:443",
+ "span.type": "external",
+ "id": ">192.0.2.162:443",
+ "label": ">192.0.2.162:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.202:443",
+ "id": "artifact_api~>192.0.2.202:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.202:443",
+ "span.type": "external",
+ "id": ">192.0.2.202:443",
+ "label": ">192.0.2.202:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.60:443",
+ "id": "artifact_api~>192.0.2.60:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.60:443",
+ "span.type": "external",
+ "id": ">192.0.2.60:443",
+ "label": ">192.0.2.60:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.59:443",
+ "id": "artifact_api~>192.0.2.59:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.59:443",
+ "span.type": "external",
+ "id": ">192.0.2.59:443",
+ "label": ">192.0.2.59:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.114:443",
+ "id": "artifact_api~>192.0.2.114:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.114:443",
+ "span.type": "external",
+ "id": ">192.0.2.114:443",
+ "label": ">192.0.2.114:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.215:443",
+ "id": "artifact_api~>192.0.2.215:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.215:443",
+ "span.type": "external",
+ "id": ">192.0.2.215:443",
+ "label": ">192.0.2.215:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.238:443",
+ "id": "artifact_api~>192.0.2.238:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.238:443",
+ "span.type": "external",
+ "id": ">192.0.2.238:443",
+ "label": ">192.0.2.238:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.160:443",
+ "id": "artifact_api~>192.0.2.160:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.160:443",
+ "span.type": "external",
+ "id": ">192.0.2.160:443",
+ "label": ">192.0.2.160:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "source": "artifact_api",
+ "target": ">192.0.2.70:443",
+ "id": "artifact_api~>192.0.2.70:443",
+ "sourceData": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ },
+ "targetData": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.70:443",
+ "span.type": "external",
+ "id": ">192.0.2.70:443",
+ "label": ">192.0.2.70:443"
+ }
+ }
+ },
+ {
+ "data": {
+ "id": "artifact_api",
+ "service.environment": "development",
+ "service.name": "artifact_api",
+ "agent.name": "nodejs",
+ "service.framework.name": "express"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "http",
+ "span.destination.service.resource": "192.0.2.99:80",
+ "span.type": "external",
+ "id": ">192.0.2.99:80",
+ "label": ">192.0.2.99:80"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.186:443",
+ "span.type": "external",
+ "id": ">192.0.2.186:443",
+ "label": ">192.0.2.186:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.78:443",
+ "span.type": "external",
+ "id": ">192.0.2.78:443",
+ "label": ">192.0.2.78:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.226:443",
+ "span.type": "external",
+ "id": ">192.0.2.226:443",
+ "label": ">192.0.2.226:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.245:443",
+ "span.type": "external",
+ "id": ">192.0.2.245:443",
+ "label": ">192.0.2.245:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.77:443",
+ "span.type": "external",
+ "id": ">192.0.2.77:443",
+ "label": ">192.0.2.77:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.2:443",
+ "span.type": "external",
+ "id": ">192.0.2.2:443",
+ "label": ">192.0.2.2:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.198:443",
+ "span.type": "external",
+ "id": ">192.0.2.198:443",
+ "label": ">192.0.2.198:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.113:443",
+ "span.type": "external",
+ "id": ">192.0.2.113:443",
+ "label": ">192.0.2.113:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.39:443",
+ "span.type": "external",
+ "id": ">192.0.2.39:443",
+ "label": ">192.0.2.39:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.83:443",
+ "span.type": "external",
+ "id": ">192.0.2.83:443",
+ "label": ">192.0.2.83:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.5:443",
+ "span.type": "external",
+ "id": ">192.0.2.5:443",
+ "label": ">192.0.2.5:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.165:443",
+ "span.type": "external",
+ "id": ">192.0.2.165:443",
+ "label": ">192.0.2.165:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.156:443",
+ "span.type": "external",
+ "id": ">192.0.2.156:443",
+ "label": ">192.0.2.156:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.132:443",
+ "span.type": "external",
+ "id": ">192.0.2.132:443",
+ "label": ">192.0.2.132:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.240:443",
+ "span.type": "external",
+ "id": ">192.0.2.240:443",
+ "label": ">192.0.2.240:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.54:443",
+ "span.type": "external",
+ "id": ">192.0.2.54:443",
+ "label": ">192.0.2.54:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.213:443",
+ "span.type": "external",
+ "id": ">192.0.2.213:443",
+ "label": ">192.0.2.213:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.81:443",
+ "span.type": "external",
+ "id": ">192.0.2.81:443",
+ "label": ">192.0.2.81:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.176:443",
+ "span.type": "external",
+ "id": ">192.0.2.176:443",
+ "label": ">192.0.2.176:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.82:443",
+ "span.type": "external",
+ "id": ">192.0.2.82:443",
+ "label": ">192.0.2.82:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.23:443",
+ "span.type": "external",
+ "id": ">192.0.2.23:443",
+ "label": ">192.0.2.23:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.189:443",
+ "span.type": "external",
+ "id": ">192.0.2.189:443",
+ "label": ">192.0.2.189:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.190:443",
+ "span.type": "external",
+ "id": ">192.0.2.190:443",
+ "label": ">192.0.2.190:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.119:443",
+ "span.type": "external",
+ "id": ">192.0.2.119:443",
+ "label": ">192.0.2.119:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.169:443",
+ "span.type": "external",
+ "id": ">192.0.2.169:443",
+ "label": ">192.0.2.169:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.210:443",
+ "span.type": "external",
+ "id": ">192.0.2.210:443",
+ "label": ">192.0.2.210:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.148:443",
+ "span.type": "external",
+ "id": ">192.0.2.148:443",
+ "label": ">192.0.2.148:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.26:443",
+ "span.type": "external",
+ "id": ">192.0.2.26:443",
+ "label": ">192.0.2.26:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.139:443",
+ "span.type": "external",
+ "id": ">192.0.2.139:443",
+ "label": ">192.0.2.139:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.111:443",
+ "span.type": "external",
+ "id": ">192.0.2.111:443",
+ "label": ">192.0.2.111:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.13:443",
+ "span.type": "external",
+ "id": ">192.0.2.13:443",
+ "label": ">192.0.2.13:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.36:443",
+ "span.type": "external",
+ "id": ">192.0.2.36:443",
+ "label": ">192.0.2.36:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.69:443",
+ "span.type": "external",
+ "id": ">192.0.2.69:443",
+ "label": ">192.0.2.69:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.173:443",
+ "span.type": "external",
+ "id": ">192.0.2.173:443",
+ "label": ">192.0.2.173:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.144:443",
+ "span.type": "external",
+ "id": ">192.0.2.144:443",
+ "label": ">192.0.2.144:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.135:443",
+ "span.type": "external",
+ "id": ">192.0.2.135:443",
+ "label": ">192.0.2.135:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.21:443",
+ "span.type": "external",
+ "id": ">192.0.2.21:443",
+ "label": ">192.0.2.21:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.118:443",
+ "span.type": "external",
+ "id": ">192.0.2.118:443",
+ "label": ">192.0.2.118:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.42:443",
+ "span.type": "external",
+ "id": ">192.0.2.42:443",
+ "label": ">192.0.2.42:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.106:443",
+ "span.type": "external",
+ "id": ">192.0.2.106:443",
+ "label": ">192.0.2.106:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.3:443",
+ "span.type": "external",
+ "id": ">192.0.2.3:443",
+ "label": ">192.0.2.3:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.34:443",
+ "span.type": "external",
+ "id": ">192.0.2.34:443",
+ "label": ">192.0.2.34:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.185:443",
+ "span.type": "external",
+ "id": ">192.0.2.185:443",
+ "label": ">192.0.2.185:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.153:443",
+ "span.type": "external",
+ "id": ">192.0.2.153:443",
+ "label": ">192.0.2.153:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.9:443",
+ "span.type": "external",
+ "id": ">192.0.2.9:443",
+ "label": ">192.0.2.9:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.164:443",
+ "span.type": "external",
+ "id": ">192.0.2.164:443",
+ "label": ">192.0.2.164:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.47:443",
+ "span.type": "external",
+ "id": ">192.0.2.47:443",
+ "label": ">192.0.2.47:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.45:443",
+ "span.type": "external",
+ "id": ">192.0.2.45:443",
+ "label": ">192.0.2.45:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.8:443",
+ "span.type": "external",
+ "id": ">192.0.2.8:443",
+ "label": ">192.0.2.8:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.103:443",
+ "span.type": "external",
+ "id": ">192.0.2.103:443",
+ "label": ">192.0.2.103:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.60:443",
+ "span.type": "external",
+ "id": ">192.0.2.60:443",
+ "label": ">192.0.2.60:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.202:443",
+ "span.type": "external",
+ "id": ">192.0.2.202:443",
+ "label": ">192.0.2.202:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.70:443",
+ "span.type": "external",
+ "id": ">192.0.2.70:443",
+ "label": ">192.0.2.70:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.114:443",
+ "span.type": "external",
+ "id": ">192.0.2.114:443",
+ "label": ">192.0.2.114:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.25:443",
+ "span.type": "external",
+ "id": ">192.0.2.25:443",
+ "label": ">192.0.2.25:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.209:443",
+ "span.type": "external",
+ "id": ">192.0.2.209:443",
+ "label": ">192.0.2.209:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.248:443",
+ "span.type": "external",
+ "id": ">192.0.2.248:443",
+ "label": ">192.0.2.248:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.18:443",
+ "span.type": "external",
+ "id": ">192.0.2.18:443",
+ "label": ">192.0.2.18:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.107:443",
+ "span.type": "external",
+ "id": ">192.0.2.107:443",
+ "label": ">192.0.2.107:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.160:443",
+ "span.type": "external",
+ "id": ">192.0.2.160:443",
+ "label": ">192.0.2.160:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.228:443",
+ "span.type": "external",
+ "id": ">192.0.2.228:443",
+ "label": ">192.0.2.228:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.215:443",
+ "span.type": "external",
+ "id": ">192.0.2.215:443",
+ "label": ">192.0.2.215:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.162:443",
+ "span.type": "external",
+ "id": ">192.0.2.162:443",
+ "label": ">192.0.2.162:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.238:443",
+ "span.type": "external",
+ "id": ">192.0.2.238:443",
+ "label": ">192.0.2.238:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.145:443",
+ "span.type": "external",
+ "id": ">192.0.2.145:443",
+ "label": ">192.0.2.145:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.239:443",
+ "span.type": "external",
+ "id": ">192.0.2.239:443",
+ "label": ">192.0.2.239:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.59:443",
+ "span.type": "external",
+ "id": ">192.0.2.59:443",
+ "label": ">192.0.2.59:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.71:443",
+ "span.type": "external",
+ "id": ">192.0.2.71:443",
+ "label": ">192.0.2.71:443"
+ }
+ },
+ {
+ "data": {
+ "span.subtype": "https",
+ "span.destination.service.resource": "192.0.2.48:443",
+ "span.type": "external",
+ "id": ">192.0.2.48:443",
+ "label": ">192.0.2.48:443"
+ }
+ },
+ {
+ "data": {
+ "service.name": "graphics-worker",
+ "agent.name": "nodejs",
+ "service.environment": null,
+ "service.framework.name": null,
+ "id": "graphics-worker"
+ }
+ }
+ ]
+}
diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
index 7c69fb28d668f..920ef39e84ca3 100644
--- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
+++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx
@@ -21,13 +21,13 @@ import {
ApmPluginContext,
ApmPluginContextValue,
} from '../../../../../context/ApmPluginContext';
+import { EuiThemeProvider } from '../../../../../../../observability/public';
storiesOf(
'app/Settings/AgentConfigurations/AgentConfigurationCreateEdit',
module
-).add(
- 'with config',
- () => {
+)
+ .addDecorator((storyFn) => {
const httpMock = {};
// mock
@@ -40,10 +40,21 @@ storiesOf(
},
},
};
+
return (
-
+
+
+ {storyFn()}
+
+
+ );
+ })
+ .add(
+ 'with config',
+ () => {
+ return (
-
- );
- },
- {
- info: {
- source: false,
+ );
},
- }
-);
+ {
+ info: {
+ source: false,
+ },
+ }
+ );
From 9ce10b732a662e05820b423941d07455192d7146 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Cau=C3=AA=20Marcondes?=
<55978943+cauemarcondes@users.noreply.github.com>
Date: Wed, 24 Jun 2020 16:08:27 +0100
Subject: [PATCH 02/82] adding Stats interface with type (#69784) (#69795)
---
.../typings/fetch_data_response/index.d.ts | 37 +++++++------------
1 file changed, 14 insertions(+), 23 deletions(-)
diff --git a/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts b/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts
index 30ecb24a58a5a..06e86d1096cfc 100644
--- a/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts
+++ b/x-pack/plugins/observability/public/typings/fetch_data_response/index.d.ts
@@ -4,17 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-interface Percentage {
- label: string;
- pct: number;
- color?: string;
-}
-interface Bytes {
- label: string;
- bytes: number;
- color?: string;
-}
-interface Numeral {
+interface Stat {
+ type: 'number' | 'percent' | 'bytesPerSecond';
label: string;
value: number;
color?: string;
@@ -37,18 +28,18 @@ export interface FetchDataResponse {
}
export interface LogsFetchDataResponse extends FetchDataResponse {
- stats: Record;
+ stats: Record;
series: Record;
}
export interface MetricsFetchDataResponse extends FetchDataResponse {
stats: {
- hosts: Numeral;
- cpu: Percentage;
- memory: Percentage;
- disk: Percentage;
- inboundTraffic: Bytes;
- outboundTraffic: Bytes;
+ hosts: Stat;
+ cpu: Stat;
+ memory: Stat;
+ disk: Stat;
+ inboundTraffic: Stat;
+ outboundTraffic: Stat;
};
series: {
inboundTraffic: Series;
@@ -58,9 +49,9 @@ export interface MetricsFetchDataResponse extends FetchDataResponse {
export interface UptimeFetchDataResponse extends FetchDataResponse {
stats: {
- monitors: Numeral;
- up: Numeral;
- down: Numeral;
+ monitors: Stat;
+ up: Stat;
+ down: Stat;
};
series: {
up: Series;
@@ -70,8 +61,8 @@ export interface UptimeFetchDataResponse extends FetchDataResponse {
export interface ApmFetchDataResponse extends FetchDataResponse {
stats: {
- services: Numeral;
- transactions: Numeral;
+ services: Stat;
+ transactions: Stat;
};
series: {
transactions: Series;
From 15ad35485415eb76424cfaf8ab1408e95a7dddca Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Wed, 24 Jun 2020 10:18:39 -0500
Subject: [PATCH 03/82] [7.x] [chore] TS 3.9: convert ts-ignore to
ts-expect-error (#69541) (#69771)
Co-authored-by: Elastic Machine
---
.../functions/browser/markdown.ts | 2 +-
.../functions/common/compare.ts | 8 +++---
.../functions/common/containerStyle.ts | 2 +-
.../functions/common/image.ts | 4 +--
.../functions/common/math.ts | 3 +--
.../functions/common/palette.ts | 2 +-
.../canvas_plugin_src/functions/common/pie.ts | 6 ++---
.../functions/common/plot/index.ts | 6 ++---
.../functions/common/render.ts | 1 -
.../functions/common/repeatImage.ts | 4 +--
.../functions/common/revealImage.ts | 4 +--
.../functions/common/saved_visualization.ts | 2 +-
.../functions/common/staticColumn.ts | 1 -
.../functions/server/demodata/index.ts | 2 +-
.../functions/server/escount.ts | 2 +-
.../functions/server/esdocs.ts | 2 +-
.../functions/server/essql.ts | 2 +-
.../functions/server/get_field_names.test.ts | 2 +-
.../functions/server/pointseries/index.ts | 11 +++-----
.../pointseries/lib/is_column_reference.ts | 2 +-
.../canvas/canvas_plugin_src/plugin.ts | 9 +++----
.../input_type_to_expression/visualization.ts | 2 +-
.../uis/arguments/date_format/index.ts | 1 -
.../canvas_plugin_src/uis/arguments/index.ts | 24 ++++++++---------
.../uis/arguments/number_format/index.ts | 1 -
.../canvas_plugin_src/uis/views/index.ts | 27 +++++++++----------
.../plugins/canvas/common/lib/autocomplete.ts | 2 +-
x-pack/plugins/canvas/common/lib/dataurl.ts | 2 +-
x-pack/plugins/canvas/common/lib/index.ts | 26 +++++++-----------
.../common/lib/pivot_object_array.test.ts | 2 +-
x-pack/plugins/canvas/public/application.tsx | 10 +++----
.../export/__tests__/export_app.test.tsx | 2 +-
.../workpad/workpad_app/workpad_telemetry.tsx | 3 ---
.../arg_add_popover/arg_add_popover.tsx | 6 ++---
.../public/components/asset_manager/asset.tsx | 1 -
.../components/asset_manager/asset_modal.tsx | 2 --
.../public/components/asset_manager/index.ts | 8 +++---
.../components/asset_picker/asset_picker.tsx | 9 +------
.../custom_element_modal.tsx | 2 --
.../components/embeddable_flyout/index.tsx | 2 +-
.../components/file_upload/file_upload.tsx | 1 -
.../components/font_picker/font_picker.tsx | 1 -
.../canvas/public/components/router/index.ts | 4 +--
.../components/saved_elements_modal/index.ts | 5 ++--
.../element_settings/element_settings.tsx | 4 +--
.../components/sidebar/global_config.tsx | 7 +++--
.../public/components/sidebar/sidebar.tsx | 2 +-
.../public/components/toolbar/toolbar.tsx | 8 +++---
.../workpad_header/edit_menu/index.ts | 12 ++++-----
.../element_menu/element_menu.tsx | 1 -
.../workpad_header/element_menu/index.tsx | 4 +--
.../fullscreen_control/fullscreen_control.tsx | 2 +-
.../components/workpad_header/index.tsx | 3 ---
.../workpad_header/refresh_control/index.ts | 3 +--
.../workpad_header/share_menu/flyout/index.ts | 1 -
.../workpad_header/share_menu/utils.ts | 1 -
.../workpad_header/view_menu/index.ts | 4 +--
.../workpad_header/workpad_header.tsx | 5 ++--
.../interaction_boundary.tsx | 2 +-
.../workpad_shortcuts/workpad_shortcuts.tsx | 2 +-
.../extended_template.examples.tsx | 2 +-
.../__examples__/simple_template.examples.tsx | 2 +-
.../__examples__/simple_template.examples.tsx | 2 +-
.../plugins/canvas/public/functions/asset.ts | 2 +-
.../canvas/public/functions/filters.ts | 2 +-
.../canvas/public/functions/timelion.ts | 2 +-
x-pack/plugins/canvas/public/functions/to.ts | 2 +-
x-pack/plugins/canvas/public/lib/app_state.ts | 6 ++---
.../public/lib/build_embeddable_filters.ts | 2 +-
.../canvas/public/lib/clipboard.test.ts | 2 +-
.../canvas/public/lib/clone_subgraphs.ts | 2 +-
.../plugins/canvas/public/lib/create_thunk.ts | 2 +-
.../canvas/public/lib/download_workpad.ts | 2 +-
.../public/lib/element_handler_creators.ts | 2 --
.../plugins/canvas/public/lib/es_service.ts | 1 -
.../public/lib/sync_filter_expression.ts | 1 -
x-pack/plugins/canvas/public/plugin.tsx | 2 +-
x-pack/plugins/canvas/public/registries.ts | 10 +++----
.../canvas/public/state/actions/embeddable.ts | 2 +-
.../canvas/public/state/actions/workpad.ts | 2 +-
.../__tests__/workpad_autoplay.test.ts | 2 +-
.../__tests__/workpad_refresh.test.ts | 1 -
.../public/state/middleware/in_flight.ts | 2 +-
.../state/middleware/workpad_autoplay.ts | 4 +--
.../state/middleware/workpad_refresh.ts | 5 ++--
.../public/state/reducers/embeddable.ts | 2 +-
.../public/state/selectors/resolved_args.ts | 4 +--
.../canvas/public/state/selectors/workpad.ts | 4 +--
x-pack/plugins/canvas/public/store.ts | 4 +--
.../server/routes/es_fields/es_fields.ts | 2 +-
.../server/routes/shareables/download.ts | 1 -
.../server/sample_data/load_sample_data.ts | 5 ++--
.../api/__tests__/shareable.test.tsx | 2 +-
.../rendered_element.examples.tsx | 2 +-
.../footer/settings/autoplay_settings.tsx | 1 -
.../components/rendered_element.tsx | 6 ++---
.../shareable_runtime/supported_renderers.js | 4 ---
.../plugins/canvas/shareable_runtime/types.ts | 2 +-
98 files changed, 164 insertions(+), 220 deletions(-)
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts
index 41323a82f4ee0..e44fb903ef042 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/browser/markdown.ts
@@ -10,7 +10,7 @@ import {
Style,
ExpressionFunctionDefinition,
} from 'src/plugins/expressions/common';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { Handlebars } from '../../../common/lib/handlebars';
import { getFunctionHelp } from '../../../i18n';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.ts
index e952faca1d5eb..8a28f71ee1b47 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/compare.ts
@@ -59,25 +59,25 @@ export function compare(): ExpressionFunctionDefinition<'compare', Context, Argu
return a !== b;
case Operation.LT:
if (typesMatch) {
- // @ts-ignore #35433 This is a wonky comparison for nulls
+ // @ts-expect-error #35433 This is a wonky comparison for nulls
return a < b;
}
return false;
case Operation.LTE:
if (typesMatch) {
- // @ts-ignore #35433 This is a wonky comparison for nulls
+ // @ts-expect-error #35433 This is a wonky comparison for nulls
return a <= b;
}
return false;
case Operation.GT:
if (typesMatch) {
- // @ts-ignore #35433 This is a wonky comparison for nulls
+ // @ts-expect-error #35433 This is a wonky comparison for nulls
return a > b;
}
return false;
case Operation.GTE:
if (typesMatch) {
- // @ts-ignore #35433 This is a wonky comparison for nulls
+ // @ts-expect-error #35433 This is a wonky comparison for nulls
return a >= b;
}
return false;
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts
index b841fde284ab6..09ce2b2bf1755 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/containerStyle.ts
@@ -6,7 +6,7 @@
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { ContainerStyle, Overflow, BackgroundRepeat, BackgroundSize } from '../../../types';
import { getFunctionHelp, getFunctionErrors } from '../../../i18n';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { isValidUrl } from '../../../common/lib/url';
interface Output extends ContainerStyle {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts
index c43ff6373ea0f..3ef956b41ce20 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/image.ts
@@ -6,9 +6,9 @@
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { getFunctionHelp, getFunctionErrors } from '../../../i18n';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl';
-// @ts-ignore .png file
+// @ts-expect-error .png file
import { elasticLogo } from '../../lib/elastic_logo';
export enum ImageMode {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts
index 7f84dc54d8092..e36644530eae8 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts
@@ -4,9 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore no @typed def; Elastic library
+// @ts-expect-error no @typed def; Elastic library
import { evaluate } from 'tinymath';
-// @ts-ignore untyped local
import { pivotObjectArray } from '../../../common/lib/pivot_object_array';
import { Datatable, isDatatable, ExpressionFunctionDefinition } from '../../../types';
import { getFunctionHelp, getFunctionErrors } from '../../../i18n';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts
index 63cd663d2ac4c..f27abe261e2e2 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts
@@ -5,7 +5,7 @@
*/
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { palettes } from '../../../common/lib/palettes';
import { getFunctionHelp } from '../../../i18n';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts
index 6cb64a43ea582..b568f18924869 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/pie.ts
@@ -5,11 +5,11 @@
*/
import { get, map, groupBy } from 'lodash';
-// @ts-ignore lodash.keyby imports invalid member from @types/lodash
+// @ts-expect-error lodash.keyby imports invalid member from @types/lodash
import keyBy from 'lodash.keyby';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { getColorsFromPalette } from '../../../common/lib/get_colors_from_palette';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { getLegendConfig } from '../../../common/lib/get_legend_config';
import { getFunctionHelp } from '../../../i18n';
import {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts
index e8214ca8eaf9f..0b4583f4581ae 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/index.ts
@@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore no @typed def
+// @ts-expect-error no @typed def
import keyBy from 'lodash.keyby';
import { groupBy, get, set, map, sortBy } from 'lodash';
import { ExpressionFunctionDefinition, Style } from 'src/plugins/expressions';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { getColorsFromPalette } from '../../../../common/lib/get_colors_from_palette';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { getLegendConfig } from '../../../../common/lib/get_legend_config';
import { getFlotAxisConfig } from './get_flot_axis_config';
import { getFontSpec } from './get_font_spec';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts
index da50195480c68..f8eeabfccde6d 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/render.ts
@@ -7,7 +7,6 @@
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
import { Render, ContainerStyle } from '../../../types';
import { getFunctionHelp } from '../../../i18n';
-// @ts-ignore unconverted local file
import { DEFAULT_ELEMENT_CSS } from '../../../common/lib/constants';
interface ContainerStyleArgument extends ContainerStyle {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts
index f91fd3dfc5522..9e296f2b9a92a 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/repeatImage.ts
@@ -5,9 +5,9 @@
*/
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl';
-// @ts-ignore .png file
+// @ts-expect-error .png file
import { elasticOutline } from '../../lib/elastic_outline';
import { Render } from '../../../types';
import { getFunctionHelp } from '../../../i18n';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts
index d961227a302b8..3e721cc49b411 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/revealImage.ts
@@ -5,9 +5,9 @@
*/
import { ExpressionFunctionDefinition, ExpressionValueRender } from 'src/plugins/expressions';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { resolveWithMissingImage } from '../../../common/lib/resolve_dataurl';
-// @ts-ignore .png file
+// @ts-expect-error .png file
import { elasticOutline } from '../../lib/elastic_outline';
import { getFunctionHelp, getFunctionErrors } from '../../../i18n';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts
index 62328556b45e3..da441bc3266c1 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/saved_visualization.ts
@@ -79,7 +79,7 @@ export function savedVisualization(): ExpressionFunctionDefinition<
}
if (hideLegend === true) {
- // @ts-ignore LegendOpen missing on VisualizeInput
+ // @ts-expect-error LegendOpen missing on VisualizeInput
visOptions.legendOpen = false;
}
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts
index 9dd38dd57c677..4fa4be0a2f09f 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.ts
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore untyped Elastic library
import { getType } from '@kbn/interpreter/common';
import {
ExpressionFunctionDefinition,
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts
index 843e2bda47e12..60d5edeb10483 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts
@@ -6,7 +6,7 @@
import { sortBy } from 'lodash';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions';
-// @ts-ignore unconverted lib file
+// @ts-expect-error unconverted lib file
import { queryDatatable } from '../../../../common/lib/datatable/query';
import { DemoRows } from './demo_rows_types';
import { getDemoRows } from './get_demo_rows';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts
index 142331aabf351..26f651e770363 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/escount.ts
@@ -9,7 +9,7 @@ import {
ExpressionValueFilter,
} from 'src/plugins/expressions/common';
/* eslint-disable */
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { buildESRequest } from '../../../server/lib/build_es_request';
/* eslint-enable */
import { getFunctionHelp } from '../../../i18n';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts
index 2b229b8957ec1..a090f09a76ea2 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/esdocs.ts
@@ -7,7 +7,7 @@
import squel from 'squel';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions';
/* eslint-disable */
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { queryEsSQL } from '../../../server/lib/query_es_sql';
/* eslint-enable */
import { ExpressionValueFilter } from '../../../types';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts
index c64398d4b3a18..5ac91bec849c2 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/essql.ts
@@ -6,7 +6,7 @@
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
/* eslint-disable */
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { queryEsSQL } from '../../../server/lib/query_es_sql';
/* eslint-enable */
import { ExpressionValueFilter } from '../../../types';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts
index c25628e5cf2b9..7dee587895485 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore untyped library
+// @ts-expect-error untyped library
import { parse } from 'tinymath';
import { getFieldNames } from './pointseries/lib/get_field_names';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
index 54e48c8abf04b..bae80d3c33510 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts
@@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore Untyped library
+// @ts-expect-error untyped library
import uniqBy from 'lodash.uniqby';
-// @ts-ignore Untyped Elastic library
+// @ts-expect-error untyped Elastic library
import { evaluate } from 'tinymath';
import { groupBy, zipObject, omit } from 'lodash';
import moment from 'moment';
@@ -18,13 +18,10 @@ import {
PointSeriesColumnName,
PointSeriesColumns,
} from 'src/plugins/expressions/common';
-// @ts-ignore Untyped local
import { pivotObjectArray } from '../../../../common/lib/pivot_object_array';
-// @ts-ignore Untyped local
import { unquoteString } from '../../../../common/lib/unquote_string';
-// @ts-ignore Untyped local
import { isColumnReference } from './lib/is_column_reference';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { getExpressionType } from './lib/get_expression_type';
import { getFunctionHelp, getFunctionErrors } from '../../../../i18n';
@@ -125,7 +122,7 @@ export function pointseries(): ExpressionFunctionDefinition<
col.role = 'measure';
}
- // @ts-ignore untyped local: get_expression_type
+ // @ts-expect-error untyped local: get_expression_type
columns[argName] = col;
}
});
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts
index 0ecc135ba9042..aed9861e1250c 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore Untyped Library
+// @ts-expect-error untyped library
import { parse } from 'tinymath';
export function isColumnReference(mathExpression: string | null): boolean {
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts
index c9ce4d065968a..4fbb5d0069e51 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/plugin.ts
@@ -12,17 +12,16 @@ import { Start as InspectorStart } from '../../../../src/plugins/inspector/publi
import { functions } from './functions/browser';
import { typeFunctions } from './expression_types';
-// @ts-ignore: untyped local
+// @ts-expect-error: untyped local
import { renderFunctions, renderFunctionFactories } from './renderers';
import { initializeElements } from './elements';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { transformSpecs } from './uis/transforms';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { datasourceSpecs } from './uis/datasources';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { modelSpecs } from './uis/models';
import { initializeViews } from './uis/views';
-// @ts-ignore Untyped Local
import { initializeArgs } from './uis/arguments';
import { tagSpecs } from './uis/tags';
import { templateSpecs } from './templates';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts
index 4c8de2afd81ad..f03c10e2d424e 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/embeddable/input_type_to_expression/visualization.ts
@@ -26,7 +26,7 @@ export function toExpression(input: VisualizeInput): string {
.reduce((_, part) => expressionParts.push(part), 0);
}
- // @ts-ignore LegendOpen missing on VisualizeInput type
+ // @ts-expect-error LegendOpen missing on VisualizeInput type
if (input.vis?.legendOpen !== undefined && input.vis.legendOpen === false) {
expressionParts.push(`hideLegend=true`);
}
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts
index fce9b21fa0387..e972928fe20b0 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/date_format/index.ts
@@ -7,7 +7,6 @@
import { compose, withProps } from 'recompose';
import moment from 'moment';
import { DateFormatArgInput as Component, Props as ComponentProps } from './date_format';
-// @ts-ignore untyped local lib
import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component';
import { ArgumentFactory } from '../../../../types/arguments';
import { ArgumentStrings } from '../../../../i18n';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts
index 2f9a21d8a009f..94a9cf28aef69 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts
@@ -5,31 +5,31 @@
*/
import { axisConfig } from './axis_config';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { datacolumn } from './datacolumn';
import { dateFormatInitializer } from './date_format';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { filterGroup } from './filter_group';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { imageUpload } from './image_upload';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { number } from './number';
import { numberFormatInitializer } from './number_format';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { palette } from './palette';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { percentage } from './percentage';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { range } from './range';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { select } from './select';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { shape } from './shape';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { string } from './string';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { textarea } from './textarea';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { toggle } from './toggle';
import { SetupInitializer } from '../../plugin';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts
index 5a3e3904f4f23..17d630f0ab9e2 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/number_format/index.ts
@@ -6,7 +6,6 @@
import { compose, withProps } from 'recompose';
import { NumberFormatArgInput as Component, Props as ComponentProps } from './number_format';
-// @ts-ignore untyped local lib
import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component';
import { ArgumentFactory } from '../../../../types/arguments';
import { ArgumentStrings } from '../../../../i18n';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts
index 34877f2fd551b..19f10628a90cb 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/views/index.ts
@@ -4,33 +4,32 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { dropdownControl } from './dropdownControl';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { getCell } from './getCell';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { image } from './image';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { markdown } from './markdown';
-// @ts-ignore untyped local
import { metricInitializer } from './metric';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { pie } from './pie';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { plot } from './plot';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { progress } from './progress';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { repeatImage } from './repeatImage';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { revealImage } from './revealImage';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { render } from './render';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { shape } from './shape';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { table } from './table';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { timefilterControl } from './timefilterControl';
import { SetupInitializer } from '../../plugin';
diff --git a/x-pack/plugins/canvas/common/lib/autocomplete.ts b/x-pack/plugins/canvas/common/lib/autocomplete.ts
index c97879de2137e..0a30b2e2f598e 100644
--- a/x-pack/plugins/canvas/common/lib/autocomplete.ts
+++ b/x-pack/plugins/canvas/common/lib/autocomplete.ts
@@ -5,7 +5,7 @@
*/
import { uniq } from 'lodash';
-// @ts-ignore Untyped Library
+// @ts-expect-error untyped library
import { parse } from '@kbn/interpreter/common';
import {
ExpressionAstExpression,
diff --git a/x-pack/plugins/canvas/common/lib/dataurl.ts b/x-pack/plugins/canvas/common/lib/dataurl.ts
index ea5a26b27e423..60e65a6d3ca1c 100644
--- a/x-pack/plugins/canvas/common/lib/dataurl.ts
+++ b/x-pack/plugins/canvas/common/lib/dataurl.ts
@@ -6,7 +6,7 @@
import { fromByteArray } from 'base64-js';
-// @ts-ignore @types/mime doesn't resolve mime/lite for some reason.
+// @ts-expect-error @types/mime doesn't resolve mime/lite for some reason.
import mime from 'mime/lite';
const dataurlRegex = /^data:([a-z]+\/[a-z0-9-+.]+)(;[a-z-]+=[a-z0-9-]+)?(;([a-z0-9]+))?,/;
diff --git a/x-pack/plugins/canvas/common/lib/index.ts b/x-pack/plugins/canvas/common/lib/index.ts
index 5ab29c290c3da..4cb3cbbb9b4e6 100644
--- a/x-pack/plugins/canvas/common/lib/index.ts
+++ b/x-pack/plugins/canvas/common/lib/index.ts
@@ -4,39 +4,33 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './datatable';
-// @ts-ignore missing local definition
export * from './autocomplete';
export * from './constants';
export * from './dataurl';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './errors';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './expression_form_handlers';
-// @ts-ignore missing local definition
export * from './fetch';
export * from './fonts';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './get_colors_from_palette';
-// @ts-ignore missing local definition
export * from './get_field_type';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './get_legend_config';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './handlebars';
export * from './hex_to_rgb';
-// @ts-ignore missing local definition
export * from './httpurl';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './missing_asset';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './palettes';
-// @ts-ignore missing local definition
export * from './pivot_object_array';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './resolve_dataurl';
-// @ts-ignore missing local definition
export * from './unquote_string';
-// @ts-ignore missing local definition
+// @ts-expect-error missing local definition
export * from './url';
diff --git a/x-pack/plugins/canvas/common/lib/pivot_object_array.test.ts b/x-pack/plugins/canvas/common/lib/pivot_object_array.test.ts
index faf319769cab0..0fbc2fa6b0f38 100644
--- a/x-pack/plugins/canvas/common/lib/pivot_object_array.test.ts
+++ b/x-pack/plugins/canvas/common/lib/pivot_object_array.test.ts
@@ -55,7 +55,7 @@ describe('pivotObjectArray', () => {
});
it('throws when given an invalid column list', () => {
- // @ts-ignore testing potential calls from legacy code that should throw
+ // @ts-expect-error testing potential calls from legacy code that should throw
const check = () => pivotObjectArray(rows, [{ name: 'price' }, { name: 'missing' }]);
expect(check).toThrowError('Columns should be an array of strings');
});
diff --git a/x-pack/plugins/canvas/public/application.tsx b/x-pack/plugins/canvas/public/application.tsx
index c799f36a283c1..b2c836fe4805f 100644
--- a/x-pack/plugins/canvas/public/application.tsx
+++ b/x-pack/plugins/canvas/public/application.tsx
@@ -15,14 +15,14 @@ import { BehaviorSubject } from 'rxjs';
import { AppMountParameters, CoreStart, CoreSetup, AppUpdater } from 'kibana/public';
import { CanvasStartDeps, CanvasSetupDeps } from './plugin';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { App } from './components/app';
import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public';
import { registerLanguage } from './lib/monaco_language_def';
import { SetupRegistries } from './plugin_api';
import { initRegistries, populateRegistries, destroyRegistries } from './registries';
import { getDocumentationLinks } from './lib/documentation_links';
-// @ts-ignore untyped component
+// @ts-expect-error untyped component
import { HelpMenu } from './components/help_menu/help_menu';
import { createStore } from './store';
@@ -32,12 +32,12 @@ import { init as initStatsReporter } from './lib/ui_metric';
import { CapabilitiesStrings } from '../i18n';
import { startServices, services } from './services';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { createHistory, destroyHistory } from './lib/history_provider';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { stopRouter } from './lib/router_provider';
import { initFunctions } from './functions';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { appUnload } from './state/actions/app';
import './style/index.scss';
diff --git a/x-pack/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx b/x-pack/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx
index 7f5b53df4ba52..b0a8d1e990e75 100644
--- a/x-pack/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx
+++ b/x-pack/plugins/canvas/public/apps/export/export/__tests__/export_app.test.tsx
@@ -6,7 +6,7 @@
import React from 'react';
import { mount } from 'enzyme';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { ExportApp } from '../export_app';
jest.mock('style-it', () => ({
diff --git a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx
index 3014369d94857..981334ff8d9f2 100644
--- a/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx
+++ b/x-pack/plugins/canvas/public/apps/workpad/workpad_app/workpad_telemetry.tsx
@@ -6,11 +6,8 @@
import React, { useState, useEffect } from 'react';
import { connect, ConnectedProps } from 'react-redux';
-// @ts-ignore: Local Untyped
import { trackCanvasUiMetric, METRIC_TYPE } from '../../../lib/ui_metric';
-// @ts-ignore: Local Untyped
import { getElementCounts } from '../../../state/selectors/workpad';
-// @ts-ignore: Local Untyped
import { getArgs } from '../../../state/selectors/resolved_args';
const WorkpadLoadedMetric = 'workpad-loaded';
diff --git a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx
index c26fdb8c46d0f..26295acecd920 100644
--- a/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx
+++ b/x-pack/plugins/canvas/public/components/arg_add_popover/arg_add_popover.tsx
@@ -7,11 +7,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import { EuiButtonIcon } from '@elastic/eui';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { Popover, PopoverChildrenProps } from '../popover';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { ArgAdd } from '../arg_add';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { Arg } from '../../expression_types/arg';
import { ComponentStrings } from '../../../i18n';
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset.tsx
index cb7ec1aba8f59..b0eaecc7b5203 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/asset.tsx
+++ b/x-pack/plugins/canvas/public/components/asset_manager/asset.tsx
@@ -7,7 +7,6 @@ import {
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
- // @ts-ignore (elastic/eui#1262) EuiImage is not exported yet
EuiImage,
EuiPanel,
EuiSpacer,
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx b/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx
index bf34f60ce75b3..ebc398a805b62 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx
+++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_modal.tsx
@@ -6,7 +6,6 @@
import {
EuiButton,
EuiEmptyPrompt,
- // @ts-ignore (elastic/eui#1557) EuiFilePicker is not exported yet
EuiFilePicker,
EuiFlexGrid,
EuiFlexGroup,
@@ -26,7 +25,6 @@ import React, { FunctionComponent } from 'react';
import { ComponentStrings } from '../../../i18n';
-// @ts-ignore
import { ASSET_MAX_SIZE } from '../../../common/lib/constants';
import { Loading } from '../loading';
import { Asset } from './asset';
diff --git a/x-pack/plugins/canvas/public/components/asset_manager/index.ts b/x-pack/plugins/canvas/public/components/asset_manager/index.ts
index 23dbe3df085d4..b07857f13f6c6 100644
--- a/x-pack/plugins/canvas/public/components/asset_manager/index.ts
+++ b/x-pack/plugins/canvas/public/components/asset_manager/index.ts
@@ -9,16 +9,16 @@ import { compose, withProps } from 'recompose';
import { set, get } from 'lodash';
import { fromExpression, toExpression } from '@kbn/interpreter/common';
import { getAssets } from '../../state/selectors/assets';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { removeAsset, createAsset } from '../../state/actions/assets';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { elementsRegistry } from '../../lib/elements_registry';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { addElement } from '../../state/actions/elements';
import { getSelectedPage } from '../../state/selectors/workpad';
import { encode } from '../../../common/lib/dataurl';
import { getId } from '../../lib/get_id';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { findExistingAsset } from '../../lib/find_existing_asset';
import { VALID_IMAGE_TYPES } from '../../../common/lib/constants';
import { withKibana } from '../../../../../../src/plugins/kibana_react/public';
diff --git a/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx b/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx
index 4489e877abf88..1f49e9ae14f5c 100644
--- a/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx
+++ b/x-pack/plugins/canvas/public/components/asset_picker/asset_picker.tsx
@@ -6,14 +6,7 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
-import {
- EuiFlexGrid,
- EuiFlexItem,
- EuiLink,
- // @ts-ignore (elastic/eui#1557) EuiImage is not exported yet
- EuiImage,
- EuiIcon,
-} from '@elastic/eui';
+import { EuiFlexGrid, EuiFlexItem, EuiLink, EuiImage, EuiIcon } from '@elastic/eui';
import { CanvasAsset } from '../../../types';
diff --git a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx
index 8f73939de69a6..ceb7c83f3cab5 100644
--- a/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx
+++ b/x-pack/plugins/canvas/public/components/custom_element_modal/custom_element_modal.tsx
@@ -12,7 +12,6 @@ import {
EuiButton,
EuiButtonEmpty,
EuiFieldText,
- // @ts-ignore hasn't been converted to TypeScript yet
EuiFilePicker,
EuiFlexGroup,
EuiFlexItem,
@@ -27,7 +26,6 @@ import {
EuiTextArea,
EuiTitle,
} from '@elastic/eui';
-// @ts-ignore converting /libs/constants to TS breaks CI
import { VALID_IMAGE_TYPES } from '../../../common/lib/constants';
import { encode } from '../../../common/lib/dataurl';
import { ElementCard } from '../element_card';
diff --git a/x-pack/plugins/canvas/public/components/embeddable_flyout/index.tsx b/x-pack/plugins/canvas/public/components/embeddable_flyout/index.tsx
index 8e69396f67c2e..9462ba0411de4 100644
--- a/x-pack/plugins/canvas/public/components/embeddable_flyout/index.tsx
+++ b/x-pack/plugins/canvas/public/components/embeddable_flyout/index.tsx
@@ -10,7 +10,7 @@ import { compose } from 'recompose';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { AddEmbeddableFlyout, Props } from './flyout';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { addElement } from '../../state/actions/elements';
import { getSelectedPage } from '../../state/selectors/workpad';
import { EmbeddableTypes } from '../../../canvas_plugin_src/expression_types/embeddable';
diff --git a/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx b/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx
index 993ee8bde2653..22fa32606407b 100644
--- a/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx
+++ b/x-pack/plugins/canvas/public/components/file_upload/file_upload.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore (elastic/eui#1262) EuiFilePicker is not exported yet
import { EuiFilePicker } from '@elastic/eui';
import PropTypes from 'prop-types';
import React, { FunctionComponent } from 'react';
diff --git a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx
index 4340430829342..556a3c5452160 100644
--- a/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx
+++ b/x-pack/plugins/canvas/public/components/font_picker/font_picker.tsx
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore (elastic/eui#1262) EuiSuperSelect is not exported yet
import { EuiSuperSelect } from '@elastic/eui';
import PropTypes from 'prop-types';
import React, { FunctionComponent } from 'react';
diff --git a/x-pack/plugins/canvas/public/components/router/index.ts b/x-pack/plugins/canvas/public/components/router/index.ts
index fa857c6f0cd3c..561ad0e9401f5 100644
--- a/x-pack/plugins/canvas/public/components/router/index.ts
+++ b/x-pack/plugins/canvas/public/components/router/index.ts
@@ -5,14 +5,14 @@
*/
import { connect } from 'react-redux';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { setFullscreen } from '../../state/actions/transient';
import {
enableAutoplay,
setRefreshInterval,
setAutoplayInterval,
} from '../../state/actions/workpad';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { Router as Component } from './router';
import { State } from '../../../types';
diff --git a/x-pack/plugins/canvas/public/components/saved_elements_modal/index.ts b/x-pack/plugins/canvas/public/components/saved_elements_modal/index.ts
index f14fc92e028db..c5c1dbc2fdd6e 100644
--- a/x-pack/plugins/canvas/public/components/saved_elements_modal/index.ts
+++ b/x-pack/plugins/canvas/public/components/saved_elements_modal/index.ts
@@ -8,14 +8,13 @@ import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import { compose, withState } from 'recompose';
import { camelCase } from 'lodash';
-// @ts-ignore Untyped local
import { cloneSubgraphs } from '../../lib/clone_subgraphs';
import * as customElementService from '../../lib/custom_element_service';
import { withKibana } from '../../../../../../src/plugins/kibana_react/public';
import { WithKibanaProps } from '../../';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { selectToplevelNodes } from '../../state/actions/transient';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { insertNodes } from '../../state/actions/elements';
import { getSelectedPage } from '../../state/selectors/workpad';
import { trackCanvasUiMetric, METRIC_TYPE } from '../../lib/ui_metric';
diff --git a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx
index 74f4887601d30..e3f4e00f4de01 100644
--- a/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx
+++ b/x-pack/plugins/canvas/public/components/sidebar/element_settings/element_settings.tsx
@@ -7,9 +7,9 @@
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
import { EuiTabbedContent } from '@elastic/eui';
-// @ts-ignore unconverted component
+// @ts-expect-error unconverted component
import { Datasource } from '../../datasource';
-// @ts-ignore unconverted component
+// @ts-expect-error unconverted component
import { FunctionFormList } from '../../function_form_list';
import { PositionedElement } from '../../../../types';
import { ComponentStrings } from '../../../../i18n';
diff --git a/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx b/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx
index 2e241681ccc6a..f89ab79a086cf 100644
--- a/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx
+++ b/x-pack/plugins/canvas/public/components/sidebar/global_config.tsx
@@ -5,13 +5,12 @@
*/
import React, { Fragment, FunctionComponent } from 'react';
-// @ts-ignore unconverted component
+// @ts-expect-error unconverted component
import { ElementConfig } from '../element_config';
-// @ts-ignore unconverted component
+// @ts-expect-error unconverted component
import { PageConfig } from '../page_config';
-// @ts-ignore unconverted component
import { WorkpadConfig } from '../workpad_config';
-// @ts-ignore unconverted component
+// @ts-expect-error unconverted component
import { SidebarSection } from './sidebar_section';
export const GlobalConfig: FunctionComponent = () => (
diff --git a/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx b/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx
index 26f106911e015..9f1936fdc143b 100644
--- a/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx
+++ b/x-pack/plugins/canvas/public/components/sidebar/sidebar.tsx
@@ -5,7 +5,7 @@
*/
import React, { FunctionComponent } from 'react';
-// @ts-ignore unconverted component
+// @ts-expect-error unconverted component
import { SidebarContent } from './sidebar_content';
interface Props {
diff --git a/x-pack/plugins/canvas/public/components/toolbar/toolbar.tsx b/x-pack/plugins/canvas/public/components/toolbar/toolbar.tsx
index 0f8204e6bc261..9a26b438e17c3 100644
--- a/x-pack/plugins/canvas/public/components/toolbar/toolbar.tsx
+++ b/x-pack/plugins/canvas/public/components/toolbar/toolbar.tsx
@@ -20,13 +20,13 @@ import { CanvasElement } from '../../../types';
import { ComponentStrings } from '../../../i18n';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { Navbar } from '../navbar';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { WorkpadManager } from '../workpad_manager';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { PageManager } from '../page_manager';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { Expression } from '../expression';
import { Tray } from './tray';
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts
index 75bdcd2b0ada1..8f013f70aefcd 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/edit_menu/index.ts
@@ -9,17 +9,17 @@ import { compose, withHandlers, withProps } from 'recompose';
import { Dispatch } from 'redux';
import { State, PositionedElement } from '../../../../types';
import { getClipboardData } from '../../../lib/clipboard';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { flatten } from '../../../lib/aeroelastic/functional';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { globalStateUpdater } from '../../workpad_page/integration_utils';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { crawlTree } from '../../workpad_page/integration_utils';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { insertNodes, elementLayer, removeElements } from '../../../state/actions/elements';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { undoHistory, redoHistory } from '../../../state/actions/history';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { selectToplevelNodes } from '../../../state/actions/transient';
import {
getSelectedPage,
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx
index fbb5d70dfc55c..6d9233aaba22b 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx
@@ -19,7 +19,6 @@ import { ElementSpec } from '../../../../types';
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
import { getId } from '../../../lib/get_id';
import { Popover, ClosePopoverFn } from '../../popover';
-// @ts-ignore Untyped local
import { AssetManager } from '../../asset_manager';
import { SavedElementsModal } from '../../saved_elements_modal';
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/index.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/index.tsx
index a1227b3394678..13b2cace13a40 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/index.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/index.tsx
@@ -10,10 +10,10 @@ import { compose, withProps } from 'recompose';
import { Dispatch } from 'redux';
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public/';
import { State, ElementSpec } from '../../../../types';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { elementsRegistry } from '../../../lib/elements_registry';
import { ElementMenu as Component, Props as ComponentProps } from './element_menu';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { addElement } from '../../../state/actions/elements';
import { getSelectedPage } from '../../../state/selectors/workpad';
import { AddEmbeddablePanel } from '../../embeddable_flyout';
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.tsx b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.tsx
index 5ffa712abee13..77edf9d2264d4 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/fullscreen_control/fullscreen_control.tsx
@@ -6,7 +6,7 @@
import React, { ReactNode, KeyboardEvent } from 'react';
import PropTypes from 'prop-types';
-// @ts-ignore no @types definition
+// @ts-expect-error no @types definition
import { Shortcuts } from 'react-shortcuts';
import { isTextInput } from '../../../lib/is_text_input';
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/index.tsx b/x-pack/plugins/canvas/public/components/workpad_header/index.tsx
index d2fece567a8ad..407b4ff932811 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/index.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/index.tsx
@@ -6,11 +6,8 @@
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
-// @ts-ignore untyped local
import { canUserWrite } from '../../state/selectors/app';
-// @ts-ignore untyped local
import { getSelectedPage, isWriteable } from '../../state/selectors/workpad';
-// @ts-ignore untyped local
import { setWriteable } from '../../state/actions/workpad';
import { State } from '../../../types';
import { WorkpadHeader as Component, Props as ComponentProps } from './workpad_header';
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/index.ts
index 53c053811a273..87b926d93ccb9 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/index.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/refresh_control/index.ts
@@ -5,9 +5,8 @@
*/
import { connect } from 'react-redux';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { fetchAllRenderables } from '../../../state/actions/elements';
-// @ts-ignore untyped local
import { getInFlight } from '../../../state/selectors/resolved_args';
import { State } from '../../../../types';
import { RefreshControl as Component } from './refresh_control';
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts
index 64712f0df8d6c..1e1eac2a1dcf3 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/flyout/index.ts
@@ -11,7 +11,6 @@ import {
getRenderedWorkpad,
getRenderedWorkpadExpressions,
} from '../../../../state/selectors/workpad';
-// @ts-ignore Untyped local
import {
downloadRenderedWorkpad,
downloadRuntime,
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts
index 8a3438e89e846..45257cd4fe308 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/share_menu/utils.ts
@@ -5,7 +5,6 @@
*/
import rison from 'rison-node';
-// @ts-ignore Untyped local.
import { IBasePath } from 'kibana/public';
import { fetch } from '../../../../common/lib/fetch';
import { CanvasWorkpad } from '../../../../types';
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts
index 0765973915f77..ddf1a12775cae 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts
+++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/index.ts
@@ -10,9 +10,9 @@ import { Dispatch } from 'redux';
import { withKibana } from '../../../../../../../src/plugins/kibana_react/public/';
import { zoomHandlerCreators } from '../../../lib/app_handler_creators';
import { State, CanvasWorkpadBoundingBox } from '../../../../types';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { fetchAllRenderables } from '../../../state/actions/elements';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { setZoomScale, setFullscreen, selectToplevelNodes } from '../../../state/actions/transient';
import {
setWriteable,
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx
index 4aab8280a9f24..eb4b451896b46 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx
@@ -6,14 +6,13 @@
import React, { FunctionComponent } from 'react';
import PropTypes from 'prop-types';
-// @ts-ignore no @types definition
+// @ts-expect-error no @types definition
import { Shortcuts } from 'react-shortcuts';
import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import { ComponentStrings } from '../../../i18n';
import { ToolTipShortcut } from '../tool_tip_shortcut/';
-// @ts-ignore untyped local
import { RefreshControl } from './refresh_control';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { FullscreenControl } from './fullscreen_control';
import { EditMenu } from './edit_menu';
import { ElementMenu } from './element_menu';
diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx
index d5841a1069ea1..e1ed7c7db84a0 100644
--- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_interactive_page/interaction_boundary.tsx
@@ -5,7 +5,7 @@
*/
import React, { CSSProperties, PureComponent } from 'react';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { WORKPAD_CONTAINER_ID } from '../../../apps/workpad/workpad_app';
interface State {
diff --git a/x-pack/plugins/canvas/public/components/workpad_shortcuts/workpad_shortcuts.tsx b/x-pack/plugins/canvas/public/components/workpad_shortcuts/workpad_shortcuts.tsx
index f9e0ec8a8a541..1bb3ef330f846 100644
--- a/x-pack/plugins/canvas/public/components/workpad_shortcuts/workpad_shortcuts.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_shortcuts/workpad_shortcuts.tsx
@@ -7,7 +7,7 @@
import React, { Component, KeyboardEvent } from 'react';
import isEqual from 'react-fast-compare';
-// @ts-ignore no @types definition
+// @ts-expect-error no @types definition
import { Shortcuts } from 'react-shortcuts';
import { isTextInput } from '../../lib/is_text_input';
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx
index 5fdc88ed62406..863cdd88163c2 100644
--- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx
+++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/extended_template.examples.tsx
@@ -7,7 +7,7 @@
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { getDefaultWorkpad } from '../../../../state/defaults';
import { Arguments, ArgumentTypes, BorderStyle, ExtendedTemplate } from '../extended_template';
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx
index 4ef17fbe87616..2dbff1b4d916b 100644
--- a/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx
+++ b/x-pack/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/simple_template.examples.tsx
@@ -7,7 +7,7 @@
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { getDefaultWorkpad } from '../../../../state/defaults';
import { Argument, Arguments, SimpleTemplate } from '../simple_template';
diff --git a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx
index f9b175e84ec8e..fa1b2420d46d2 100644
--- a/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx
+++ b/x-pack/plugins/canvas/public/expression_types/arg_types/series_style/__examples__/simple_template.examples.tsx
@@ -7,7 +7,7 @@
import { action } from '@storybook/addon-actions';
import { storiesOf } from '@storybook/react';
import React from 'react';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { getDefaultWorkpad } from '../../../../state/defaults';
import { SimpleTemplate } from '../simple_template';
diff --git a/x-pack/plugins/canvas/public/functions/asset.ts b/x-pack/plugins/canvas/public/functions/asset.ts
index 2f2ad181b264c..ebd3fd2abdcbb 100644
--- a/x-pack/plugins/canvas/public/functions/asset.ts
+++ b/x-pack/plugins/canvas/public/functions/asset.ts
@@ -5,7 +5,7 @@
*/
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public';
-// @ts-ignore unconverted local lib
+// @ts-expect-error unconverted local lib
import { getState } from '../state/store';
import { getAssetById } from '../state/selectors/assets';
import { getFunctionHelp, getFunctionErrors } from '../../i18n';
diff --git a/x-pack/plugins/canvas/public/functions/filters.ts b/x-pack/plugins/canvas/public/functions/filters.ts
index 78cd742b44b26..48f4a41c7690a 100644
--- a/x-pack/plugins/canvas/public/functions/filters.ts
+++ b/x-pack/plugins/canvas/public/functions/filters.ts
@@ -8,7 +8,7 @@ import { fromExpression } from '@kbn/interpreter/common';
import { get } from 'lodash';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public';
import { interpretAst } from '../lib/run_interpreter';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { getState } from '../state/store';
import { getGlobalFilters } from '../state/selectors/workpad';
import { ExpressionValueFilter } from '../../types';
diff --git a/x-pack/plugins/canvas/public/functions/timelion.ts b/x-pack/plugins/canvas/public/functions/timelion.ts
index abb294d9cc110..4eb34e838d18a 100644
--- a/x-pack/plugins/canvas/public/functions/timelion.ts
+++ b/x-pack/plugins/canvas/public/functions/timelion.ts
@@ -9,7 +9,7 @@ import moment from 'moment-timezone';
import { TimeRange } from 'src/plugins/data/common';
import { ExpressionFunctionDefinition, DatatableRow } from 'src/plugins/expressions/public';
import { fetch } from '../../common/lib/fetch';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { buildBoolArray } from '../../public/lib/build_bool_array';
import { Datatable, ExpressionValueFilter } from '../../types';
import { getFunctionHelp } from '../../i18n';
diff --git a/x-pack/plugins/canvas/public/functions/to.ts b/x-pack/plugins/canvas/public/functions/to.ts
index 64d25b28a8aa0..032873dfa6cf2 100644
--- a/x-pack/plugins/canvas/public/functions/to.ts
+++ b/x-pack/plugins/canvas/public/functions/to.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore untyped Elastic library
+// @ts-expect-error untyped Elastic library
import { castProvider } from '@kbn/interpreter/common';
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/public';
import { getFunctionHelp, getFunctionErrors } from '../../i18n';
diff --git a/x-pack/plugins/canvas/public/lib/app_state.ts b/x-pack/plugins/canvas/public/lib/app_state.ts
index d431202ba75a4..a09df3c8cb87d 100644
--- a/x-pack/plugins/canvas/public/lib/app_state.ts
+++ b/x-pack/plugins/canvas/public/lib/app_state.ts
@@ -6,12 +6,12 @@
import { parse } from 'query-string';
import { get } from 'lodash';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { getInitialState } from '../state/initial_state';
import { getWindow } from './get_window';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { historyProvider } from './history_provider';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { routerProvider } from './router_provider';
import { createTimeInterval, isValidTimeInterval, getTimeInterval } from './time_interval';
import { AppState, AppStateKeys } from '../../types';
diff --git a/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts b/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts
index c847bfb6516bf..94d0d16bf79f6 100644
--- a/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts
+++ b/x-pack/plugins/canvas/public/lib/build_embeddable_filters.ts
@@ -5,7 +5,7 @@
*/
import { ExpressionValueFilter } from '../../types';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { buildBoolArray } from './build_bool_array';
import { TimeRange, esFilters, Filter as DataFilter } from '../../../../../src/plugins/data/public';
diff --git a/x-pack/plugins/canvas/public/lib/clipboard.test.ts b/x-pack/plugins/canvas/public/lib/clipboard.test.ts
index d10964003ed39..53f92e2184edc 100644
--- a/x-pack/plugins/canvas/public/lib/clipboard.test.ts
+++ b/x-pack/plugins/canvas/public/lib/clipboard.test.ts
@@ -15,7 +15,7 @@ const get = jest.fn();
describe('clipboard', () => {
beforeAll(() => {
- // @ts-ignore
+ // @ts-expect-error
Storage.mockImplementation(() => ({
set,
get,
diff --git a/x-pack/plugins/canvas/public/lib/clone_subgraphs.ts b/x-pack/plugins/canvas/public/lib/clone_subgraphs.ts
index c3a3933e06a6d..7168272211d44 100644
--- a/x-pack/plugins/canvas/public/lib/clone_subgraphs.ts
+++ b/x-pack/plugins/canvas/public/lib/clone_subgraphs.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { arrayToMap } from './aeroelastic/functional';
import { getId } from './get_id';
import { PositionedElement } from '../../types';
diff --git a/x-pack/plugins/canvas/public/lib/create_thunk.ts b/x-pack/plugins/canvas/public/lib/create_thunk.ts
index cbcaeeccc8b93..8ce912246ad6f 100644
--- a/x-pack/plugins/canvas/public/lib/create_thunk.ts
+++ b/x-pack/plugins/canvas/public/lib/create_thunk.ts
@@ -5,7 +5,7 @@
*/
import { Dispatch, Action } from 'redux';
-// @ts-ignore untyped dependency
+// @ts-expect-error untyped dependency
import { createThunk as createThunkFn } from 'redux-thunks/cjs';
import { State } from '../../types';
diff --git a/x-pack/plugins/canvas/public/lib/download_workpad.ts b/x-pack/plugins/canvas/public/lib/download_workpad.ts
index fb038d8b6ace2..d0a63cf3fb5c4 100644
--- a/x-pack/plugins/canvas/public/lib/download_workpad.ts
+++ b/x-pack/plugins/canvas/public/lib/download_workpad.ts
@@ -7,7 +7,7 @@ import fileSaver from 'file-saver';
import { API_ROUTE_SHAREABLE_RUNTIME_DOWNLOAD } from '../../common/lib/constants';
import { ErrorStrings } from '../../i18n';
import { notifyService } from '../services';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import * as workpadService from './workpad_service';
import { CanvasRenderedWorkpad } from '../../shareable_runtime/types';
diff --git a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts
index a2bf5a62ec1f7..8f1a0f0ecf08f 100644
--- a/x-pack/plugins/canvas/public/lib/element_handler_creators.ts
+++ b/x-pack/plugins/canvas/public/lib/element_handler_creators.ts
@@ -5,9 +5,7 @@
*/
import { camelCase } from 'lodash';
-// @ts-ignore unconverted local file
import { getClipboardData, setClipboardData } from './clipboard';
-// @ts-ignore unconverted local file
import { cloneSubgraphs } from './clone_subgraphs';
import { notifyService } from '../services';
import * as customElementService from './custom_element_service';
diff --git a/x-pack/plugins/canvas/public/lib/es_service.ts b/x-pack/plugins/canvas/public/lib/es_service.ts
index 496751a874b21..5c1131d5fbe35 100644
--- a/x-pack/plugins/canvas/public/lib/es_service.ts
+++ b/x-pack/plugins/canvas/public/lib/es_service.ts
@@ -7,7 +7,6 @@
import { IndexPatternAttributes } from 'src/plugins/data/public';
import { API_ROUTE } from '../../common/lib/constants';
-// @ts-ignore untyped local
import { fetch } from '../../common/lib/fetch';
import { ErrorStrings } from '../../i18n';
import { notifyService } from '../services';
diff --git a/x-pack/plugins/canvas/public/lib/sync_filter_expression.ts b/x-pack/plugins/canvas/public/lib/sync_filter_expression.ts
index dc70f778f0e52..4bfe6ff4b141f 100644
--- a/x-pack/plugins/canvas/public/lib/sync_filter_expression.ts
+++ b/x-pack/plugins/canvas/public/lib/sync_filter_expression.ts
@@ -4,7 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore internal untyped
import { fromExpression } from '@kbn/interpreter/common';
import immutable from 'object-path-immutable';
import { get } from 'lodash';
diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx
index 9d2a6b3fdf4f4..4829a94bb0db8 100644
--- a/x-pack/plugins/canvas/public/plugin.tsx
+++ b/x-pack/plugins/canvas/public/plugin.tsx
@@ -24,7 +24,7 @@ import { UiActionsStart } from '../../../../src/plugins/ui_actions/public';
import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
import { Start as InspectorStart } from '../../../../src/plugins/inspector/public';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { argTypeSpecs } from './expression_types/arg_types';
import { transitions } from './transitions';
import { getPluginApi, CanvasApi } from './plugin_api';
diff --git a/x-pack/plugins/canvas/public/registries.ts b/x-pack/plugins/canvas/public/registries.ts
index 99f309a917329..b2881fc0b7799 100644
--- a/x-pack/plugins/canvas/public/registries.ts
+++ b/x-pack/plugins/canvas/public/registries.ts
@@ -4,14 +4,14 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// @ts-ignore untyped module
+// @ts-expect-error untyped module
import { addRegistries, register } from '@kbn/interpreter/common';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { elementsRegistry } from './lib/elements_registry';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { templatesRegistry } from './lib/templates_registry';
import { tagsRegistry } from './lib/tags_registry';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { transitionsRegistry } from './lib/transitions_registry';
import {
@@ -20,7 +20,7 @@ import {
modelRegistry,
transformRegistry,
viewRegistry,
- // @ts-ignore untyped local
+ // @ts-expect-error untyped local
} from './expression_types';
import { SetupRegistries } from './plugin_api';
diff --git a/x-pack/plugins/canvas/public/state/actions/embeddable.ts b/x-pack/plugins/canvas/public/state/actions/embeddable.ts
index a153cb7f4354d..874d390277320 100644
--- a/x-pack/plugins/canvas/public/state/actions/embeddable.ts
+++ b/x-pack/plugins/canvas/public/state/actions/embeddable.ts
@@ -7,7 +7,7 @@
import { Dispatch } from 'redux';
import { createAction } from 'redux-actions';
import { createThunk } from '../../lib/create_thunk';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { fetchRenderable } from './elements';
import { State } from '../../../types';
diff --git a/x-pack/plugins/canvas/public/state/actions/workpad.ts b/x-pack/plugins/canvas/public/state/actions/workpad.ts
index 47df38838f890..419832e404594 100644
--- a/x-pack/plugins/canvas/public/state/actions/workpad.ts
+++ b/x-pack/plugins/canvas/public/state/actions/workpad.ts
@@ -8,7 +8,7 @@ import { createAction } from 'redux-actions';
import { without, includes } from 'lodash';
import { createThunk } from '../../lib/create_thunk';
import { getWorkpadColors } from '../selectors/workpad';
-// @ts-ignore
+// @ts-expect-error
import { fetchAllRenderables } from './elements';
import { CanvasWorkpad } from '../../../types';
diff --git a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts
index 11ebdcdc51d4d..bb7b26919ef20 100644
--- a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts
+++ b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_autoplay.test.ts
@@ -10,7 +10,7 @@ jest.mock('../../../lib/router_provider');
import { workpadAutoplay } from '../workpad_autoplay';
import { setAutoplayInterval } from '../../../lib/app_state';
import { createTimeInterval } from '../../../lib/time_interval';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { routerProvider } from '../../../lib/router_provider';
const next = jest.fn();
diff --git a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts
index f90f570bc6ebf..bf69a862d5c30 100644
--- a/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts
+++ b/x-pack/plugins/canvas/public/state/middleware/__tests__/workpad_refresh.test.ts
@@ -9,7 +9,6 @@ jest.mock('../../../lib/app_state');
import { workpadRefresh } from '../workpad_refresh';
import { inFlightComplete } from '../../actions/resolved_args';
-// @ts-ignore untyped local
import { setRefreshInterval } from '../../actions/workpad';
import { setRefreshInterval as setAppStateRefreshInterval } from '../../../lib/app_state';
diff --git a/x-pack/plugins/canvas/public/state/middleware/in_flight.ts b/x-pack/plugins/canvas/public/state/middleware/in_flight.ts
index 7ad6f8aee15ed..028b9f214133f 100644
--- a/x-pack/plugins/canvas/public/state/middleware/in_flight.ts
+++ b/x-pack/plugins/canvas/public/state/middleware/in_flight.ts
@@ -9,7 +9,7 @@ import {
loadingIndicator as defaultLoadingIndicator,
LoadingIndicatorInterface,
} from '../../lib/loading_indicator';
-// @ts-ignore
+// @ts-expect-error
import { convert } from '../../lib/modify_path';
interface InFlightMiddlewareOptions {
diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts b/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts
index dd484521c1b35..f77a1e1ba3295 100644
--- a/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts
+++ b/x-pack/plugins/canvas/public/state/middleware/workpad_autoplay.ts
@@ -9,9 +9,9 @@ import { State } from '../../../types';
import { getFullscreen } from '../selectors/app';
import { getInFlight } from '../selectors/resolved_args';
import { getWorkpad, getPages, getSelectedPageIndex, getAutoplay } from '../selectors/workpad';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { appUnload } from '../actions/app';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { routerProvider } from '../../lib/router_provider';
import { setAutoplayInterval } from '../../lib/app_state';
import { createTimeInterval } from '../../lib/time_interval';
diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts b/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts
index 96a84b22cfccc..4a17ffb464532 100644
--- a/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts
+++ b/x-pack/plugins/canvas/public/state/middleware/workpad_refresh.ts
@@ -6,11 +6,10 @@
import { Middleware } from 'redux';
import { State } from '../../../types';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { fetchAllRenderables } from '../actions/elements';
-// @ts-ignore Untyped Local
import { setRefreshInterval } from '../actions/workpad';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { appUnload } from '../actions/app';
import { inFlightComplete } from '../actions/resolved_args';
import { getInFlight } from '../selectors/resolved_args';
diff --git a/x-pack/plugins/canvas/public/state/reducers/embeddable.ts b/x-pack/plugins/canvas/public/state/reducers/embeddable.ts
index 8642239fa10d3..fdeb5087f26e1 100644
--- a/x-pack/plugins/canvas/public/state/reducers/embeddable.ts
+++ b/x-pack/plugins/canvas/public/state/reducers/embeddable.ts
@@ -13,7 +13,7 @@ import {
UpdateEmbeddableExpressionPayload,
} from '../actions/embeddable';
-// @ts-ignore untyped local
+// @ts-expect-error untyped local
import { assignNodeProperties } from './elements';
export const embeddableReducer = handleActions<
diff --git a/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts b/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts
index 9e2036e02f2b9..766e27d95da9b 100644
--- a/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts
+++ b/x-pack/plugins/canvas/public/state/selectors/resolved_args.ts
@@ -5,9 +5,9 @@
*/
import { get } from 'lodash';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import * as argHelper from '../../lib/resolved_arg';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { prepend } from '../../lib/modify_path';
import { State } from '../../../types';
diff --git a/x-pack/plugins/canvas/public/state/selectors/workpad.ts b/x-pack/plugins/canvas/public/state/selectors/workpad.ts
index 55bf2a7ea31f7..0f4953ff56d98 100644
--- a/x-pack/plugins/canvas/public/state/selectors/workpad.ts
+++ b/x-pack/plugins/canvas/public/state/selectors/workpad.ts
@@ -5,9 +5,9 @@
*/
import { get, omit } from 'lodash';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { safeElementFromExpression, fromExpression } from '@kbn/interpreter/common';
-// @ts-ignore Untyped Local
+// @ts-expect-error untyped local
import { append } from '../../lib/modify_path';
import { getAssets } from './assets';
import { State, CanvasWorkpad, CanvasPage, CanvasElement, ResolvedArgType } from '../../../types';
diff --git a/x-pack/plugins/canvas/public/store.ts b/x-pack/plugins/canvas/public/store.ts
index 81edec6ec539c..ef93a34296da2 100644
--- a/x-pack/plugins/canvas/public/store.ts
+++ b/x-pack/plugins/canvas/public/store.ts
@@ -9,9 +9,9 @@ import {
destroyStore as destroy,
getStore,
cloneStore,
- // @ts-ignore Untyped local
+ // @ts-expect-error untyped local
} from './state/store';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { getInitialState } from './state/initial_state';
import { CoreSetup } from '../../../../src/core/public';
diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts
index 8f3ced13895f6..7a9830124e305 100644
--- a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts
+++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts
@@ -8,7 +8,7 @@ import { mapValues, keys } from 'lodash';
import { schema } from '@kbn/config-schema';
import { API_ROUTE } from '../../../common/lib';
import { catchErrorHandler } from '../catch_error_handler';
-// @ts-ignore unconverted lib
+// @ts-expect-error unconverted lib
import { normalizeType } from '../../lib/normalize_type';
import { RouteInitializerDeps } from '..';
diff --git a/x-pack/plugins/canvas/server/routes/shareables/download.ts b/x-pack/plugins/canvas/server/routes/shareables/download.ts
index 3f331c1635e16..0c86f8472c791 100644
--- a/x-pack/plugins/canvas/server/routes/shareables/download.ts
+++ b/x-pack/plugins/canvas/server/routes/shareables/download.ts
@@ -21,7 +21,6 @@ export function initializeDownloadShareableWorkpadRoute(deps: RouteInitializerDe
//
// The option setting is not for typical use. We're using it here to avoid
// problems in Cloud environments. See elastic/kibana#47405.
- // @ts-ignore No type for inert Hapi handler
// const file = handler.file(SHAREABLE_RUNTIME_FILE, { confine: false });
const file = readFileSync(SHAREABLE_RUNTIME_FILE);
return response.ok({
diff --git a/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts b/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts
index f58111000859a..f5dcf59dcf45b 100644
--- a/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts
+++ b/x-pack/plugins/canvas/server/sample_data/load_sample_data.ts
@@ -6,7 +6,6 @@
import { SampleDataRegistrySetup } from 'src/plugins/home/server';
import { CANVAS as label } from '../../i18n';
-// @ts-ignore Untyped local
import { ecommerceSavedObjects, flightsSavedObjects, webLogsSavedObjects } from './index';
export function loadSampleData(
@@ -16,9 +15,9 @@ export function loadSampleData(
const now = new Date();
const nowTimestamp = now.toISOString();
- // @ts-ignore: Untyped local
+ // @ts-expect-error: untyped local
function updateCanvasWorkpadTimestamps(savedObjects) {
- // @ts-ignore: Untyped local
+ // @ts-expect-error: untyped local
return savedObjects.map((savedObject) => {
if (savedObject.type === 'canvas-workpad') {
savedObject.attributes['@timestamp'] = nowTimestamp;
diff --git a/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx b/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx
index d99c9b190f83d..4b3aa8dc2fb6e 100644
--- a/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx
+++ b/x-pack/plugins/canvas/shareable_runtime/api/__tests__/shareable.test.tsx
@@ -15,7 +15,7 @@ jest.mock('../../supported_renderers');
describe('Canvas Shareable Workpad API', () => {
// Mock the AJAX load of the workpad.
beforeEach(function () {
- // @ts-ignore Applying a global in Jest is alright.
+ // @ts-expect-error Applying a global in Jest is alright.
global.fetch = jest.fn().mockImplementation(() => {
const p = new Promise((resolve, _reject) => {
resolve({
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx
index 7b5a5080ae790..899edee7f0481 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx
+++ b/x-pack/plugins/canvas/shareable_runtime/components/__examples__/rendered_element.examples.tsx
@@ -7,7 +7,7 @@ import { storiesOf } from '@storybook/react';
import React from 'react';
import { ExampleContext } from '../../test/context_example';
-// @ts-ignore
+// @ts-expect-error
import { image } from '../../../canvas_plugin_src/renderers/image';
import { sharedWorkpads } from '../../test';
import { RenderedElement, RenderedElementComponent } from '../rendered_element';
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/autoplay_settings.tsx b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/autoplay_settings.tsx
index 1650cbad3a237..4c7c65511698d 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/autoplay_settings.tsx
+++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/autoplay_settings.tsx
@@ -12,7 +12,6 @@ import {
setAutoplayIntervalAction,
} from '../../../context';
import { createTimeInterval } from '../../../../public/lib/time_interval';
-// @ts-ignore Untyped local
import { CustomInterval } from '../../../../public/components/workpad_header/view_menu/custom_interval';
export type onSetAutoplayFn = (autoplay: boolean) => void;
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
index c4a009db3a376..5741f5f2d698c 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
+++ b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
@@ -5,11 +5,11 @@
*/
import React, { FC, PureComponent } from 'react';
-// @ts-ignore Untyped library
+// @ts-expect-error untyped library
import Style from 'style-it';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { Positionable } from '../../public/components/positionable/positionable';
-// @ts-ignore Untyped local
+// @ts-expect-error untyped local
import { elementToShape } from '../../public/components/workpad_page/utils';
import { CanvasRenderedElement } from '../types';
import { CanvasShareableContext, useCanvasShareableState } from '../context';
diff --git a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js
index 6238aaf5c2fe4..340d1fb418b4c 100644
--- a/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js
+++ b/x-pack/plugins/canvas/shareable_runtime/supported_renderers.js
@@ -4,10 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// This is a JS file because the renderers are not strongly-typed yet. Tagging for
-// visibility.
-// @ts-ignore Untyped local
-
import { debug } from '../canvas_plugin_src/renderers/debug';
import { error } from '../canvas_plugin_src/renderers/error';
import { image } from '../canvas_plugin_src/renderers/image';
diff --git a/x-pack/plugins/canvas/shareable_runtime/types.ts b/x-pack/plugins/canvas/shareable_runtime/types.ts
index 191c0405d2e2d..040062346c74f 100644
--- a/x-pack/plugins/canvas/shareable_runtime/types.ts
+++ b/x-pack/plugins/canvas/shareable_runtime/types.ts
@@ -5,7 +5,7 @@
*/
import { RefObject } from 'react';
-// @ts-ignore Unlinked Webpack Type
+// @ts-expect-error Unlinked Webpack Type
import ContainerStyle from 'types/interpreter';
import { SavedObject, SavedObjectAttributes } from 'src/core/public';
From cfe3083379c08cd859f743ee2e20afb8fdde234a Mon Sep 17 00:00:00 2001
From: Luke Elmers
Date: Wed, 24 Jun 2020 10:10:34 -0600
Subject: [PATCH 04/82] [data.search.aggs]: Add
AggConfig.toSerializedFieldFormat (#69114) (#69803)
---
...plugin-plugins-data-public.fieldformats.md | 1 -
...plugin-plugins-data-server.fieldformats.md | 1 -
...plugin-plugins-data-server.plugin.setup.md | 4 +-
.../loader/pipeline_helpers/index.ts | 21 ----
.../loader/pipeline_helpers/utilities.ts | 31 -----
.../data/common/field_formats/index.ts | 2 +-
.../data/common/field_formats/utils/index.ts | 1 -
.../common/field_formats/utils/serialize.ts | 53 ---------
src/plugins/data/public/index.ts | 3 -
src/plugins/data/public/public.api.md | 72 ++++++------
.../public/search/aggs/agg_config.test.ts | 109 +++++++++++++++++-
.../data/public/search/aggs/agg_config.ts | 23 +++-
.../data/public/search/aggs/agg_type.test.ts | 68 +++++++++++
.../data/public/search/aggs/agg_type.ts | 23 +++-
.../search/aggs/buckets/date_histogram.ts | 8 ++
.../public/search/aggs/buckets/date_range.ts | 6 +
.../public/search/aggs/buckets/ip_range.ts | 6 +
.../buckets/lib/time_buckets/time_buckets.ts | 2 +-
.../public/search/aggs/buckets/range.test.ts | 20 +++-
.../data/public/search/aggs/buckets/range.ts | 10 ++
.../data/public/search/aggs/buckets/terms.ts | 12 ++
.../public/search/aggs/metrics/bucket_avg.ts | 9 +-
.../public/search/aggs/metrics/bucket_max.ts | 9 +-
.../public/search/aggs/metrics/bucket_min.ts | 9 +-
.../public/search/aggs/metrics/bucket_sum.ts | 9 +-
.../public/search/aggs/metrics/cardinality.ts | 5 +
.../data/public/search/aggs/metrics/count.ts | 5 +
.../search/aggs/metrics/cumulative_sum.ts | 9 +-
.../public/search/aggs/metrics/derivative.ts | 9 +-
.../metrics/lib/parent_pipeline_agg_helper.ts | 12 ++
.../lib/sibling_pipeline_agg_helper.ts | 5 +
.../public/search/aggs/metrics/moving_avg.ts | 9 +-
.../search/aggs/metrics/percentile_ranks.ts | 5 +
.../public/search/aggs/metrics/serial_diff.ts | 9 +-
.../test_helpers/mock_agg_types_registry.ts | 22 ++++
.../data/public/search/expressions/esaggs.ts | 11 +-
src/plugins/data/server/index.ts | 2 -
src/plugins/data/server/server.api.md | 51 ++++----
.../public/application/angular/discover.js | 5 +-
.../public/legacy/build_pipeline.test.ts | 36 +++---
.../public/legacy/build_pipeline.ts | 17 +--
tsconfig.types.json | 2 +
42 files changed, 472 insertions(+), 254 deletions(-)
delete mode 100644 src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts
delete mode 100644 src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts
delete mode 100644 src/plugins/data/common/field_formats/utils/serialize.ts
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md
index d39871b99f744..b51421741933a 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.fieldformats.md
@@ -10,7 +10,6 @@
fieldFormats: {
FieldFormat: typeof FieldFormat;
FieldFormatsRegistry: typeof FieldFormatsRegistry;
- serialize: (agg: import("./search").AggConfig) => import("../../expressions").SerializedFieldFormat;
DEFAULT_CONVERTER_COLOR: {
range: string;
regex: string;
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md
index 11f18a195d271..45fc1a608e8ca 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.fieldformats.md
@@ -10,7 +10,6 @@
fieldFormats: {
FieldFormatsRegistry: typeof FieldFormatsRegistry;
FieldFormat: typeof FieldFormat;
- serializeFieldFormat: (agg: import("../public/search").AggConfig) => import("../../expressions").SerializedFieldFormat;
BoolFormat: typeof BoolFormat;
BytesFormat: typeof BytesFormat;
ColorFormat: typeof ColorFormat;
diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md
index 13c69d6bf7548..a6fdfdf6891c8 100644
--- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md
+++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md
@@ -10,7 +10,7 @@
setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): {
search: ISearchSetup;
fieldFormats: {
- register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
+ register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number;
};
};
```
@@ -27,7 +27,7 @@ setup(core: CoreSetup, { usageCollection }: DataPluginS
`{
search: ISearchSetup;
fieldFormats: {
- register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
+ register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number;
};
}`
diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts
deleted file mode 100644
index f9a2234d6e5a4..0000000000000
--- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-// eslint-disable-next-line @kbn/eslint/no-restricted-paths
-export { buildPipeline } from '../../../../../../plugins/visualizations/public/legacy/build_pipeline';
diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts
deleted file mode 100644
index cb25c66dfd2fe..0000000000000
--- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { npStart } from 'ui/new_platform';
-import { fieldFormats, IFieldFormat } from '../../../../../../plugins/data/public';
-import { SerializedFieldFormat } from '../../../../../../plugins/expressions/common/types';
-
-type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
-
-const createFormat = fieldFormats.serialize;
-const getFormat: FormatFactory = (mapping?) => {
- return npStart.plugins.data.fieldFormats.deserialize(mapping as any);
-};
-
-export { getFormat, createFormat, FormatFactory };
diff --git a/src/plugins/data/common/field_formats/index.ts b/src/plugins/data/common/field_formats/index.ts
index 5c67073c07dd5..104ff030873aa 100644
--- a/src/plugins/data/common/field_formats/index.ts
+++ b/src/plugins/data/common/field_formats/index.ts
@@ -40,7 +40,7 @@ export {
TruncateFormat,
} from './converters';
-export { getHighlightRequest, serializeFieldFormat } from './utils';
+export { getHighlightRequest } from './utils';
export { DEFAULT_CONVERTER_COLOR } from './constants/color_default';
export { FIELD_FORMAT_IDS } from './types';
diff --git a/src/plugins/data/common/field_formats/utils/index.ts b/src/plugins/data/common/field_formats/utils/index.ts
index 3832c941ffad7..eb020c17ca09c 100644
--- a/src/plugins/data/common/field_formats/utils/index.ts
+++ b/src/plugins/data/common/field_formats/utils/index.ts
@@ -22,6 +22,5 @@ import { IFieldFormat } from '../index';
export { asPrettyString } from './as_pretty_string';
export { getHighlightHtml, getHighlightRequest } from './highlight';
-export { serializeFieldFormat } from './serialize';
export type FormatFactory = (mapping?: SerializedFieldFormat) => IFieldFormat;
diff --git a/src/plugins/data/common/field_formats/utils/serialize.ts b/src/plugins/data/common/field_formats/utils/serialize.ts
deleted file mode 100644
index 1092c90d19451..0000000000000
--- a/src/plugins/data/common/field_formats/utils/serialize.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to Elasticsearch B.V. under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch B.V. licenses this file to you under
- * the Apache License, Version 2.0 (the "License"); you may
- * not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import { IAggConfig } from 'src/plugins/data/public';
-import { SerializedFieldFormat } from '../../../../expressions/common/types';
-
-export const serializeFieldFormat = (agg: IAggConfig): SerializedFieldFormat => {
- const format: SerializedFieldFormat = agg.params.field ? agg.params.field.format.toJSON() : {};
- const formats: Record SerializedFieldFormat> = {
- date_range: () => ({ id: 'date_range', params: format }),
- ip_range: () => ({ id: 'ip_range', params: format }),
- percentile_ranks: () => ({ id: 'percent' }),
- count: () => ({ id: 'number' }),
- cardinality: () => ({ id: 'number' }),
- date_histogram: () => ({
- id: 'date',
- params: {
- pattern: (agg as any).buckets.getScaledDateFormat(),
- },
- }),
- terms: () => ({
- id: 'terms',
- params: {
- id: format.id,
- otherBucketLabel: agg.params.otherBucketLabel,
- missingBucketLabel: agg.params.missingBucketLabel,
- ...format.params,
- },
- }),
- range: () => ({
- id: 'range',
- params: { id: format.id, ...format.params },
- }),
- };
-
- return formats[agg.type.name] ? formats[agg.type.name]() : format;
-};
diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts
index e57b02784ca9e..d3552cf72a27e 100644
--- a/src/plugins/data/public/index.ts
+++ b/src/plugins/data/public/index.ts
@@ -168,7 +168,6 @@ import {
UrlFormat,
StringFormat,
TruncateFormat,
- serializeFieldFormat,
} from '../common/field_formats';
import { DateFormat } from './field_formats';
@@ -179,8 +178,6 @@ export const fieldFormats = {
FieldFormat,
FieldFormatsRegistry, // exported only for tests. Consider mock.
- serialize: serializeFieldFormat,
-
DEFAULT_CONVERTER_COLOR,
HTML_CONTEXT_TYPE,
TEXT_CONTEXT_TYPE,
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index a219b6660a9e7..6d6bb09ccd7dc 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -154,6 +154,7 @@ import { SearchParams } from 'elasticsearch';
import { SearchResponse as SearchResponse_2 } from 'elasticsearch';
import { SearchShardsParams } from 'elasticsearch';
import { SearchTemplateParams } from 'elasticsearch';
+import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/public';
import { SimpleSavedObject } from 'src/core/public';
import { SnapshotCreateParams } from 'elasticsearch';
import { SnapshotCreateRepositoryParams } from 'elasticsearch';
@@ -621,7 +622,6 @@ export type FieldFormatInstanceType = (new (params?: any, getConfig?: FieldForma
export const fieldFormats: {
FieldFormat: typeof FieldFormat;
FieldFormatsRegistry: typeof FieldFormatsRegistry;
- serialize: (agg: import("./search").AggConfig) => import("../../expressions").SerializedFieldFormat;
DEFAULT_CONVERTER_COLOR: {
range: string;
regex: string;
@@ -1964,41 +1964,41 @@ export const UI_SETTINGS: {
// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "luceneStringToDsl" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/index.ts:136:21 - (ae-forgotten-export) The symbol "decorateQuery" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:178:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:236:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:376:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:376:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:376:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:376:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:378:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:379:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:388:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:389:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:390:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:394:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
-// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:177:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "getFromSavedObject" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:233:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:373:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:373:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:373:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:373:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:375:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:376:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:385:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:386:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:387:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:391:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:392:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:395:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:396:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts
+// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/query/state_sync/connect_to_query_state.ts:40:60 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:52:5 - (ae-forgotten-export) The symbol "createFiltersFromValueClickAction" needs to be exported by the entry point index.d.ts
// src/plugins/data/public/types.ts:53:5 - (ae-forgotten-export) The symbol "createFiltersFromRangeSelectAction" needs to be exported by the entry point index.d.ts
diff --git a/src/plugins/data/public/search/aggs/agg_config.test.ts b/src/plugins/data/public/search/aggs/agg_config.test.ts
index 6a0dad07b69bb..95e0b2cd27186 100644
--- a/src/plugins/data/public/search/aggs/agg_config.test.ts
+++ b/src/plugins/data/public/search/aggs/agg_config.test.ts
@@ -25,7 +25,11 @@ import { AggType } from './agg_type';
import { AggTypesRegistryStart } from './agg_types_registry';
import { mockDataServices, mockAggTypesRegistry } from './test_helpers';
import { MetricAggType } from './metrics/metric_agg_type';
-import { Field as IndexPatternField, IndexPattern } from '../../index_patterns';
+import {
+ Field as IndexPatternField,
+ IndexPattern,
+ IIndexPatternFieldList,
+} from '../../index_patterns';
import { stubIndexPatternWithFields } from '../../../public/stubs';
import { FieldFormatsStart } from '../../field_formats';
import { fieldFormatsServiceMock } from '../../field_formats/mocks';
@@ -370,6 +374,109 @@ describe('AggConfig', () => {
});
});
+ describe('#toSerializedFieldFormat', () => {
+ beforeEach(() => {
+ indexPattern.fields.getByName = identity as IIndexPatternFieldList['getByName'];
+ });
+
+ it('works with aggs that have a special format type', () => {
+ const configStates = [
+ {
+ type: 'count',
+ params: {},
+ },
+ {
+ type: 'date_histogram',
+ params: { field: '@timestamp' },
+ },
+ {
+ type: 'terms',
+ params: { field: 'machine.os.keyword' },
+ },
+ ];
+ const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats });
+
+ expect(ac.aggs.map((agg) => agg.toSerializedFieldFormat())).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "id": "number",
+ },
+ Object {
+ "id": "date",
+ "params": Object {
+ "pattern": "HH:mm:ss.SSS",
+ },
+ },
+ Object {
+ "id": "terms",
+ "params": Object {
+ "id": undefined,
+ "missingBucketLabel": "Missing",
+ "otherBucketLabel": "Other",
+ },
+ },
+ ]
+ `);
+ });
+
+ it('works with pipeline aggs', () => {
+ const configStates = [
+ {
+ type: 'max_bucket',
+ params: {
+ customMetric: {
+ type: 'cardinality',
+ params: {
+ field: 'bytes',
+ },
+ },
+ },
+ },
+ {
+ type: 'cumulative_sum',
+ params: {
+ buckets_path: '1',
+ customMetric: {
+ type: 'cardinality',
+ params: {
+ field: 'bytes',
+ },
+ },
+ },
+ },
+ {
+ type: 'percentile_ranks',
+ id: 'myMetricAgg',
+ params: {},
+ },
+ {
+ type: 'cumulative_sum',
+ params: {
+ metricAgg: 'myMetricAgg',
+ },
+ },
+ ];
+ const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats });
+
+ expect(ac.aggs.map((agg) => agg.toSerializedFieldFormat())).toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "id": "number",
+ },
+ Object {
+ "id": "number",
+ },
+ Object {
+ "id": "percent",
+ },
+ Object {
+ "id": "percent",
+ },
+ ]
+ `);
+ });
+ });
+
describe('#toExpressionAst', () => {
beforeEach(() => {
fieldFormats.getDefaultInstance = (() => ({
diff --git a/src/plugins/data/public/search/aggs/agg_config.ts b/src/plugins/data/public/search/aggs/agg_config.ts
index ee4116eefc0e2..a2b74eca58476 100644
--- a/src/plugins/data/public/search/aggs/agg_config.ts
+++ b/src/plugins/data/public/search/aggs/agg_config.ts
@@ -20,7 +20,11 @@
import _ from 'lodash';
import { i18n } from '@kbn/i18n';
import { Assign, Ensure } from '@kbn/utility-types';
-import { ExpressionAstFunction, ExpressionAstArgument } from 'src/plugins/expressions/public';
+import {
+ ExpressionAstFunction,
+ ExpressionAstArgument,
+ SerializedFieldFormat,
+} from 'src/plugins/expressions/public';
import { IAggType } from './agg_type';
import { writeParams } from './agg_params';
import { IAggConfigs } from './agg_configs';
@@ -42,7 +46,7 @@ export type AggConfigSerialized = Ensure<
type: string;
enabled?: boolean;
id?: string;
- params?: SerializableState;
+ params?: {} | SerializableState;
schema?: string;
},
SerializableState
@@ -298,8 +302,8 @@ export class AggConfig {
id: this.id,
enabled: this.enabled,
type: this.type && this.type.name,
- schema: this.schema,
params: outParams as SerializableState,
+ ...(this.schema && { schema: this.schema }),
};
}
@@ -310,6 +314,19 @@ export class AggConfig {
return this.serialize();
}
+ /**
+ * Returns a serialized field format for the field used in this agg.
+ * This can be passed to fieldFormats.deserialize to get the field
+ * format instance.
+ *
+ * @public
+ */
+ toSerializedFieldFormat():
+ | {}
+ | Ensure, SerializableState> {
+ return this.type ? this.type.getSerializedFormat(this) : {};
+ }
+
/**
* @returns Returns an ExpressionAst representing the function for this agg type.
*/
diff --git a/src/plugins/data/public/search/aggs/agg_type.test.ts b/src/plugins/data/public/search/aggs/agg_type.test.ts
index 18783bbd9a760..cc45b935d45b5 100644
--- a/src/plugins/data/public/search/aggs/agg_type.test.ts
+++ b/src/plugins/data/public/search/aggs/agg_type.test.ts
@@ -199,5 +199,73 @@ describe('AggType Class', () => {
expect(aggType.getFormat(aggConfig)).toBe('default');
});
});
+
+ describe('getSerializedFormat', () => {
+ test('returns the default serialized field format if it exists', () => {
+ const aggConfig = ({
+ params: {
+ field: {
+ format: {
+ toJSON: () => ({ id: 'format' }),
+ },
+ },
+ },
+ } as unknown) as IAggConfig;
+ const aggType = new AggType(
+ {
+ name: 'name',
+ title: 'title',
+ },
+ dependencies
+ );
+ expect(aggType.getSerializedFormat(aggConfig)).toMatchInlineSnapshot(`
+ Object {
+ "id": "format",
+ }
+ `);
+ });
+
+ test('returns an empty object if a field param does not exist', () => {
+ const aggConfig = ({
+ params: {},
+ } as unknown) as IAggConfig;
+ const aggType = new AggType(
+ {
+ name: 'name',
+ title: 'title',
+ },
+ dependencies
+ );
+ expect(aggType.getSerializedFormat(aggConfig)).toMatchInlineSnapshot(`Object {}`);
+ });
+
+ test('uses a custom getSerializedFormat function if defined', () => {
+ const aggConfig = ({
+ params: {
+ field: {
+ format: {
+ toJSON: () => ({ id: 'format' }),
+ },
+ },
+ },
+ } as unknown) as IAggConfig;
+ const getSerializedFormat = jest.fn().mockReturnValue({ id: 'hello' });
+ const aggType = new AggType(
+ {
+ name: 'name',
+ title: 'title',
+ getSerializedFormat,
+ },
+ dependencies
+ );
+ const serialized = aggType.getSerializedFormat(aggConfig);
+ expect(getSerializedFormat).toHaveBeenCalledWith(aggConfig);
+ expect(serialized).toMatchInlineSnapshot(`
+ Object {
+ "id": "hello",
+ }
+ `);
+ });
+ });
});
});
diff --git a/src/plugins/data/public/search/aggs/agg_type.ts b/src/plugins/data/public/search/aggs/agg_type.ts
index fb0cb609a08cf..e909cd8134e83 100644
--- a/src/plugins/data/public/search/aggs/agg_type.ts
+++ b/src/plugins/data/public/search/aggs/agg_type.ts
@@ -19,8 +19,10 @@
import { constant, noop, identity } from 'lodash';
import { i18n } from '@kbn/i18n';
-import { initParams } from './agg_params';
+import { SerializedFieldFormat } from 'src/plugins/expressions/public';
+
+import { initParams } from './agg_params';
import { AggConfig } from './agg_config';
import { IAggConfigs } from './agg_configs';
import { Adapters } from '../../../../../plugins/inspector/public';
@@ -57,6 +59,7 @@ export interface AggTypeConfig<
abortSignal?: AbortSignal
) => Promise;
getFormat?: (agg: TAggConfig) => IFieldFormat;
+ getSerializedFormat?: (agg: TAggConfig) => SerializedFieldFormat;
getValue?: (agg: TAggConfig, bucket: any) => any;
getKey?: (bucket: any, key: any, agg: TAggConfig) => any;
}
@@ -204,6 +207,17 @@ export class AggType<
*/
getFormat: (agg: TAggConfig) => IFieldFormat;
+ /**
+ * Get the serialized format for the values produced by this agg type,
+ * overridden by several metrics that always output a simple number.
+ * You can pass this output to fieldFormatters.deserialize to get
+ * the formatter instance.
+ *
+ * @param {agg} agg - the agg to pick a format for
+ * @return {SerializedFieldFormat}
+ */
+ getSerializedFormat: (agg: TAggConfig) => SerializedFieldFormat;
+
getValue: (agg: TAggConfig, bucket: any) => any;
getKey?: (bucket: any, key: any, agg: TAggConfig) => any;
@@ -277,6 +291,13 @@ export class AggType<
return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING);
});
+
+ this.getSerializedFormat =
+ config.getSerializedFormat ||
+ ((agg: TAggConfig) => {
+ return agg.params.field ? agg.params.field.format.toJSON() : {};
+ });
+
this.getValue = config.getValue || ((agg: TAggConfig, bucket: any) => {});
}
}
diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts
index 8a5596f669cb7..fc42d43b2fea8 100644
--- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts
+++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts
@@ -152,6 +152,14 @@ export const getDateHistogramBucketAgg = ({
(key: string) => uiSettings.get(key)
);
},
+ getSerializedFormat(agg) {
+ return {
+ id: 'date',
+ params: {
+ pattern: agg.buckets.getScaledDateFormat(),
+ },
+ };
+ },
params: [
{
name: 'field',
diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts
index 447347dbfbe10..3e14ab422ccbe 100644
--- a/src/plugins/data/public/search/aggs/buckets/date_range.ts
+++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts
@@ -70,6 +70,12 @@ export const getDateRangeBucketAgg = ({
});
return new DateRangeFormat();
},
+ getSerializedFormat(agg) {
+ return {
+ id: 'date_range',
+ params: agg.params.field ? agg.params.field.format.toJSON() : {},
+ };
+ },
makeLabel(aggConfig) {
return aggConfig.getFieldDisplayName() + ' date ranges';
},
diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/ip_range.ts
index 10fdb2d93b56e..b3e90bdf9b56a 100644
--- a/src/plugins/data/public/search/aggs/buckets/ip_range.ts
+++ b/src/plugins/data/public/search/aggs/buckets/ip_range.ts
@@ -78,6 +78,12 @@ export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketA
});
return new IpRangeFormat();
},
+ getSerializedFormat(agg) {
+ return {
+ id: 'ip_range',
+ params: agg.params.field ? agg.params.field.format.toJSON() : {},
+ };
+ },
makeLabel(aggConfig) {
return i18n.translate('data.search.aggs.buckets.ipRangeLabel', {
defaultMessage: '{fieldName} IP ranges',
diff --git a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts b/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts
index b8d6586652d6b..12197c85f4a96 100644
--- a/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts
+++ b/src/plugins/data/public/search/aggs/buckets/lib/time_buckets/time_buckets.ts
@@ -48,7 +48,7 @@ function isValidMoment(m: any): boolean {
return m && 'isValid' in m && m.isValid();
}
-export interface TimeBucketsConfig {
+export interface TimeBucketsConfig extends Record {
'histogram:maxBars': number;
'histogram:barTarget': number;
dateFormat: string;
diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts
index 4c2d3af1ab734..2b8a36f2fbdbc 100644
--- a/src/plugins/data/public/search/aggs/buckets/range.test.ts
+++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts
@@ -104,7 +104,7 @@ describe('Range Agg', () => {
);
};
- describe('formating', () => {
+ describe('formatting', () => {
test('formats bucket keys properly', () => {
const aggConfigs = getAggConfigs();
const agg = aggConfigs.aggs[0];
@@ -115,4 +115,22 @@ describe('Range Agg', () => {
expect(format(buckets[2])).toBe('≥ 2.5 KB and < +∞');
});
});
+
+ describe('getSerializedFormat', () => {
+ test('generates a serialized field format in the expected shape', () => {
+ const aggConfigs = getAggConfigs();
+ const agg = aggConfigs.aggs[0];
+ expect(agg.type.getSerializedFormat(agg)).toMatchInlineSnapshot(`
+ Object {
+ "id": "range",
+ "params": Object {
+ "id": "number",
+ "params": Object {
+ "pattern": "0,0.[000] b",
+ },
+ },
+ }
+ `);
+ });
+ });
});
diff --git a/src/plugins/data/public/search/aggs/buckets/range.ts b/src/plugins/data/public/search/aggs/buckets/range.ts
index 02aad3bd5fed1..543e5d66b9fa8 100644
--- a/src/plugins/data/public/search/aggs/buckets/range.ts
+++ b/src/plugins/data/public/search/aggs/buckets/range.ts
@@ -101,6 +101,16 @@ export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDe
formats.set(agg, aggFormat);
return aggFormat;
},
+ getSerializedFormat(agg) {
+ const format = agg.params.field ? agg.params.field.format.toJSON() : {};
+ return {
+ id: 'range',
+ params: {
+ id: format.id,
+ params: format.params,
+ },
+ };
+ },
params: [
{
name: 'field',
diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts
index 45a76f08ddd13..1e8e9ab4ef9d0 100644
--- a/src/plugins/data/public/search/aggs/buckets/terms.ts
+++ b/src/plugins/data/public/search/aggs/buckets/terms.ts
@@ -104,6 +104,18 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe
},
} as IFieldFormat;
},
+ getSerializedFormat(agg) {
+ const format = agg.params.field ? agg.params.field.format.toJSON() : {};
+ return {
+ id: 'terms',
+ params: {
+ id: format.id,
+ otherBucketLabel: agg.params.otherBucketLabel,
+ missingBucketLabel: agg.params.missingBucketLabel,
+ ...format.params,
+ },
+ };
+ },
createFilter: createFilterTerms,
postFlightRequest: async (
resp: any,
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts
index 927e9a7ae4458..38312ec5cfa81 100644
--- a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts
+++ b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts
@@ -46,14 +46,17 @@ const averageBucketTitle = i18n.translate('data.search.aggs.metrics.averageBucke
export const getBucketAvgMetricAgg = ({
getInternalStartServices,
}: BucketAvgMetricAggDependencies) => {
+ const { subtype, params, getFormat, getSerializedFormat } = siblingPipelineAggHelper;
+
return new MetricAggType(
{
name: METRIC_TYPES.AVG_BUCKET,
title: averageBucketTitle,
makeLabel: (agg) => makeNestedLabel(agg, overallAverageLabel),
- subtype: siblingPipelineAggHelper.subtype,
- params: [...siblingPipelineAggHelper.params()],
- getFormat: siblingPipelineAggHelper.getFormat,
+ subtype,
+ params: [...params()],
+ getFormat,
+ getSerializedFormat,
getValue(agg, bucket) {
const customMetric = agg.getParam('customMetric');
const customBucket = agg.getParam('customBucket');
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts
index 2b171fcbd24fd..e2c6a5105bac6 100644
--- a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts
+++ b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts
@@ -45,14 +45,17 @@ const maxBucketTitle = i18n.translate('data.search.aggs.metrics.maxBucketTitle',
export const getBucketMaxMetricAgg = ({
getInternalStartServices,
}: BucketMaxMetricAggDependencies) => {
+ const { subtype, params, getFormat, getSerializedFormat } = siblingPipelineAggHelper;
+
return new MetricAggType(
{
name: METRIC_TYPES.MAX_BUCKET,
title: maxBucketTitle,
makeLabel: (agg) => makeNestedLabel(agg, overallMaxLabel),
- subtype: siblingPipelineAggHelper.subtype,
- params: [...siblingPipelineAggHelper.params()],
- getFormat: siblingPipelineAggHelper.getFormat,
+ subtype,
+ params: [...params()],
+ getFormat,
+ getSerializedFormat,
},
{
getInternalStartServices,
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts
index e6a523eeea374..c46a3eb9425d1 100644
--- a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts
+++ b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts
@@ -45,14 +45,17 @@ const minBucketTitle = i18n.translate('data.search.aggs.metrics.minBucketTitle',
export const getBucketMinMetricAgg = ({
getInternalStartServices,
}: BucketMinMetricAggDependencies) => {
+ const { subtype, params, getFormat, getSerializedFormat } = siblingPipelineAggHelper;
+
return new MetricAggType(
{
name: METRIC_TYPES.MIN_BUCKET,
title: minBucketTitle,
makeLabel: (agg) => makeNestedLabel(agg, overallMinLabel),
- subtype: siblingPipelineAggHelper.subtype,
- params: [...siblingPipelineAggHelper.params()],
- getFormat: siblingPipelineAggHelper.getFormat,
+ subtype,
+ params: [...params()],
+ getFormat,
+ getSerializedFormat,
},
{
getInternalStartServices,
diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts
index 71c88596ea569..57212ec9ff91b 100644
--- a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts
+++ b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts
@@ -45,14 +45,17 @@ const sumBucketTitle = i18n.translate('data.search.aggs.metrics.sumBucketTitle',
export const getBucketSumMetricAgg = ({
getInternalStartServices,
}: BucketSumMetricAggDependencies) => {
+ const { subtype, params, getFormat, getSerializedFormat } = siblingPipelineAggHelper;
+
return new MetricAggType(
{
name: METRIC_TYPES.SUM_BUCKET,
title: sumBucketTitle,
makeLabel: (agg) => makeNestedLabel(agg, overallSumLabel),
- subtype: siblingPipelineAggHelper.subtype,
- params: [...siblingPipelineAggHelper.params()],
- getFormat: siblingPipelineAggHelper.getFormat,
+ subtype,
+ params: [...params()],
+ getFormat,
+ getSerializedFormat,
},
{
getInternalStartServices,
diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality.ts b/src/plugins/data/public/search/aggs/metrics/cardinality.ts
index 9ff3e84c38cd8..2855cc1b6b16e 100644
--- a/src/plugins/data/public/search/aggs/metrics/cardinality.ts
+++ b/src/plugins/data/public/search/aggs/metrics/cardinality.ts
@@ -54,6 +54,11 @@ export const getCardinalityMetricAgg = ({
return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER);
},
+ getSerializedFormat(agg) {
+ return {
+ id: 'number',
+ };
+ },
params: [
{
name: 'field',
diff --git a/src/plugins/data/public/search/aggs/metrics/count.ts b/src/plugins/data/public/search/aggs/metrics/count.ts
index bd0b83798c7db..4c7b8139b0162 100644
--- a/src/plugins/data/public/search/aggs/metrics/count.ts
+++ b/src/plugins/data/public/search/aggs/metrics/count.ts
@@ -45,6 +45,11 @@ export const getCountMetricAgg = ({ getInternalStartServices }: CountMetricAggDe
return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER);
},
+ getSerializedFormat(agg) {
+ return {
+ id: 'number',
+ };
+ },
getValue(agg, bucket) {
return bucket.doc_count;
},
diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts
index 44bfca1b6fb87..c392f44a7961e 100644
--- a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts
+++ b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts
@@ -46,14 +46,17 @@ const cumulativeSumTitle = i18n.translate('data.search.aggs.metrics.cumulativeSu
export const getCumulativeSumMetricAgg = ({
getInternalStartServices,
}: CumulativeSumMetricAggDependencies) => {
+ const { subtype, params, getFormat, getSerializedFormat } = parentPipelineAggHelper;
+
return new MetricAggType(
{
name: METRIC_TYPES.CUMULATIVE_SUM,
title: cumulativeSumTitle,
- subtype: parentPipelineAggHelper.subtype,
makeLabel: (agg) => makeNestedLabel(agg, cumulativeSumLabel),
- params: [...parentPipelineAggHelper.params()],
- getFormat: parentPipelineAggHelper.getFormat,
+ subtype,
+ params: [...params()],
+ getFormat,
+ getSerializedFormat,
},
{
getInternalStartServices,
diff --git a/src/plugins/data/public/search/aggs/metrics/derivative.ts b/src/plugins/data/public/search/aggs/metrics/derivative.ts
index edb907ca4ed41..f3c1cc9bc2977 100644
--- a/src/plugins/data/public/search/aggs/metrics/derivative.ts
+++ b/src/plugins/data/public/search/aggs/metrics/derivative.ts
@@ -46,16 +46,19 @@ const derivativeTitle = i18n.translate('data.search.aggs.metrics.derivativeTitle
export const getDerivativeMetricAgg = ({
getInternalStartServices,
}: DerivativeMetricAggDependencies) => {
+ const { subtype, params, getFormat, getSerializedFormat } = parentPipelineAggHelper;
+
return new MetricAggType(
{
name: METRIC_TYPES.DERIVATIVE,
title: derivativeTitle,
- subtype: parentPipelineAggHelper.subtype,
makeLabel(agg) {
return makeNestedLabel(agg, derivativeLabel);
},
- params: [...parentPipelineAggHelper.params()],
- getFormat: parentPipelineAggHelper.getFormat,
+ subtype,
+ params: [...params()],
+ getFormat,
+ getSerializedFormat,
},
{
getInternalStartServices,
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts b/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
index 947394c97bdcd..2a74a446ce84e 100644
--- a/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
+++ b/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts
@@ -84,4 +84,16 @@ export const parentPipelineAggHelper = {
}
return subAgg ? subAgg.type.getFormat(subAgg) : new (FieldFormat.from(identity))();
},
+
+ getSerializedFormat(agg: IMetricAggConfig) {
+ let subAgg;
+ const customMetric = agg.getParam('customMetric');
+
+ if (customMetric) {
+ subAgg = customMetric;
+ } else {
+ subAgg = agg.aggConfigs.byId(agg.getParam('metricAgg'));
+ }
+ return subAgg ? subAgg.type.getSerializedFormat(subAgg) : {};
+ },
};
diff --git a/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
index cee7841a8c3b9..8e3e0143bf915 100644
--- a/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
+++ b/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts
@@ -93,4 +93,9 @@ export const siblingPipelineAggHelper = {
? customMetric.type.getFormat(customMetric)
: new (FieldFormat.from(identity))();
},
+
+ getSerializedFormat(agg: IMetricAggConfig) {
+ const customMetric = agg.getParam('customMetric');
+ return customMetric ? customMetric.type.getSerializedFormat(customMetric) : {};
+ },
};
diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts
index 1173ae5358ee7..abad2782f9d20 100644
--- a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts
+++ b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts
@@ -48,15 +48,19 @@ const movingAvgLabel = i18n.translate('data.search.aggs.metrics.movingAvgLabel',
export const getMovingAvgMetricAgg = ({
getInternalStartServices,
}: MovingAvgMetricAggDependencies) => {
+ const { subtype, params, getFormat, getSerializedFormat } = parentPipelineAggHelper;
+
return new MetricAggType(
{
name: METRIC_TYPES.MOVING_FN,
dslName: 'moving_fn',
title: movingAvgTitle,
- subtype: parentPipelineAggHelper.subtype,
makeLabel: (agg) => makeNestedLabel(agg, movingAvgLabel),
+ subtype,
+ getFormat,
+ getSerializedFormat,
params: [
- ...parentPipelineAggHelper.params(),
+ ...params(),
{
name: 'window',
default: 5,
@@ -78,7 +82,6 @@ export const getMovingAvgMetricAgg = ({
*/
return bucket[agg.id] ? bucket[agg.id].value : null;
},
- getFormat: parentPipelineAggHelper.getFormat,
},
{
getInternalStartServices,
diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts
index c8383f6bcc3d9..c5aee380b9776 100644
--- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts
+++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts
@@ -103,6 +103,11 @@ export const getPercentileRanksMetricAgg = ({
fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER)
);
},
+ getSerializedFormat(agg) {
+ return {
+ id: 'percent',
+ };
+ },
getValue(agg, bucket) {
return getPercentileValue(agg, bucket) / 100;
},
diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts
index 00bc631cefab8..7cb1b194fed1c 100644
--- a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts
+++ b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts
@@ -46,14 +46,17 @@ const serialDiffLabel = i18n.translate('data.search.aggs.metrics.serialDiffLabel
export const getSerialDiffMetricAgg = ({
getInternalStartServices,
}: SerialDiffMetricAggDependencies) => {
+ const { subtype, params, getFormat, getSerializedFormat } = parentPipelineAggHelper;
+
return new MetricAggType(
{
name: METRIC_TYPES.SERIAL_DIFF,
title: serialDiffTitle,
- subtype: parentPipelineAggHelper.subtype,
makeLabel: (agg) => makeNestedLabel(agg, serialDiffLabel),
- params: [...parentPipelineAggHelper.params()],
- getFormat: parentPipelineAggHelper.getFormat,
+ subtype,
+ params: [...params()],
+ getFormat,
+ getSerializedFormat,
},
{
getInternalStartServices,
diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts
index 5549ffd2583b1..836aaad2cda0c 100644
--- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts
+++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts
@@ -25,6 +25,25 @@ import { MetricAggType } from '../metrics/metric_agg_type';
import { queryServiceMock } from '../../../query/mocks';
import { fieldFormatsServiceMock } from '../../../field_formats/mocks';
import { InternalStartServices } from '../../../types';
+import { TimeBucketsConfig } from '../buckets/lib/time_buckets/time_buckets';
+
+// Mocked uiSettings shared among aggs unit tests
+const mockUiSettings = jest.fn().mockImplementation((key: string) => {
+ const config: TimeBucketsConfig = {
+ 'histogram:maxBars': 4,
+ 'histogram:barTarget': 3,
+ dateFormat: 'YYYY-MM-DD',
+ 'dateFormat:scaled': [
+ ['', 'HH:mm:ss.SSS'],
+ ['PT1S', 'HH:mm:ss'],
+ ['PT1M', 'HH:mm'],
+ ['PT1H', 'YYYY-MM-DD HH:mm'],
+ ['P1DT', 'YYYY-MM-DD'],
+ ['P1YT', 'YYYY'],
+ ],
+ };
+ return config[key] ?? key;
+});
/**
* Testing utility which creates a new instance of AggTypesRegistry,
@@ -54,7 +73,10 @@ export function mockAggTypesRegistry | MetricAggTyp
});
} else {
const coreSetup = coreMock.createSetup();
+ coreSetup.uiSettings.get = mockUiSettings;
+
const coreStart = coreMock.createStart();
+ coreSetup.uiSettings.get = mockUiSettings;
const aggTypes = getAggTypes({
uiSettings: coreSetup.uiSettings,
diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts
index 153eb7de6f2de..77475cd3ce88b 100644
--- a/src/plugins/data/public/search/expressions/esaggs.ts
+++ b/src/plugins/data/public/search/expressions/esaggs.ts
@@ -32,14 +32,7 @@ import { Adapters } from '../../../../../plugins/inspector/public';
import { IAggConfigs } from '../aggs';
import { ISearchSource } from '../search_source';
import { tabifyAggResponse } from '../tabify';
-import {
- Filter,
- Query,
- serializeFieldFormat,
- TimeRange,
- IIndexPattern,
- isRangeFilter,
-} from '../../../common';
+import { Filter, Query, TimeRange, IIndexPattern, isRangeFilter } from '../../../common';
import { FilterManager, calculateBounds, getTime } from '../../query';
import { getSearchService, getQueryService, getIndexPatterns } from '../../services';
import { buildTabularInspectorData } from './build_tabular_inspector_data';
@@ -313,7 +306,7 @@ export const esaggs = (): ExpressionFunctionDefinition import("../../expressions").SerializedFieldFormat;
BoolFormat: typeof BoolFormat;
BytesFormat: typeof BytesFormat;
ColorFormat: typeof ColorFormat;
@@ -633,7 +632,7 @@ export class Plugin implements Plugin_2 {
setup(core: CoreSetup, { usageCollection }: DataPluginSetupDependencies): {
search: ISearchSetup;
fieldFormats: {
- register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number;
+ register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number;
};
};
// Warning: (ae-forgotten-export) The symbol "CoreStart" needs to be exported by the entry point index.d.ts
@@ -776,30 +775,30 @@ export const UI_SETTINGS: {
// src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "getEsQueryConfig" needs to be exported by the entry point index.d.ts
// src/plugins/data/server/index.ts:71:21 - (ae-forgotten-export) The symbol "buildEsQuery" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "FieldFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:103:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:131:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:189:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:190:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
-// src/plugins/data/server/index.ts:193:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "FieldFormatsRegistry" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "FieldFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "BoolFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "BytesFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "ColorFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "DateNanosFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "DurationFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "IpFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "NumberFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "PercentFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "RelativeDateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "SourceFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "StaticLookupFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "UrlFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "StringFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:102:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:129:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:129:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:184:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:185:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:186:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:187:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:188:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts
+// src/plugins/data/server/index.ts:191:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts
// (No @packageDocumentation comment for this package)
diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js
index 225f918c9f406..65868b0b7cd46 100644
--- a/src/plugins/discover/public/application/angular/discover.js
+++ b/src/plugins/discover/public/application/angular/discover.js
@@ -66,7 +66,6 @@ const {
import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../helpers/breadcrumbs';
import {
esFilters,
- fieldFormats,
indexPatterns as indexPatternsUtils,
connectToQueryState,
syncQueryStateWithUrl,
@@ -851,7 +850,7 @@ function discoverController(
x: {
accessor: 0,
label: agg.makeLabel(),
- format: fieldFormats.serialize(agg),
+ format: agg.toSerializedFieldFormat(),
params: {
date: true,
interval: moment.duration(esValue, esUnit),
@@ -863,7 +862,7 @@ function discoverController(
},
y: {
accessor: 1,
- format: fieldFormats.serialize(metric),
+ format: metric.toSerializedFieldFormat(),
label: metric.makeLabel(),
},
};
diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts
index 9130581963800..9ecd321963e8a 100644
--- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts
+++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts
@@ -28,7 +28,7 @@ import {
} from './build_pipeline';
import { Vis } from '..';
import { dataPluginMock } from '../../../../plugins/data/public/mocks';
-import { IAggConfig } from '../../../../plugins/data/public';
+import { IndexPattern, IAggConfigs } from '../../../../plugins/data/public';
describe('visualize loader pipeline helpers: build pipeline', () => {
describe('prepareJson', () => {
@@ -344,23 +344,20 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
describe('buildVislibDimensions', () => {
const dataStart = dataPluginMock.createStartContract();
- let aggs: IAggConfig[];
+ let aggs: IAggConfigs;
let vis: Vis;
let params: any;
beforeEach(() => {
- aggs = [
+ aggs = dataStart.search.aggs.createAggConfigs({} as IndexPattern, [
{
id: '0',
enabled: true,
- type: {
- type: 'metrics',
- name: 'count',
- },
+ type: 'count',
schema: 'metric',
params: {},
- } as IAggConfig,
- ];
+ },
+ ]);
params = {
searchSource: null,
@@ -393,11 +390,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
],
},
data: {
- aggs: {
- getResponseAggs: () => {
- return aggs;
- },
- } as any,
+ aggs,
searchSource: {} as any,
},
isHierarchical: () => {
@@ -422,8 +415,13 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
});
it('with two numeric metrics, mixed normal and percent mode should have corresponding formatters', async () => {
- const aggConfig = aggs[0];
- aggs = [{ ...aggConfig } as IAggConfig, { ...aggConfig, id: '5' } as IAggConfig];
+ aggs.createAggConfig({
+ id: '5',
+ enabled: true,
+ type: 'count',
+ schema: 'metric',
+ params: {},
+ });
vis.params = {
seriesParams: [
@@ -469,11 +467,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => {
},
params: { gauge: {} },
data: {
- aggs: {
- getResponseAggs: () => {
- return aggs;
- },
- } as any,
+ aggs,
searchSource: {} as any,
},
isHierarchical: () => {
diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts
index 5d74cb3d3b1e5..62ff1f83426b9 100644
--- a/src/plugins/visualizations/public/legacy/build_pipeline.ts
+++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts
@@ -20,12 +20,7 @@
import { get } from 'lodash';
import moment from 'moment';
import { SerializedFieldFormat } from '../../../../plugins/expressions/public';
-import {
- IAggConfig,
- fieldFormats,
- search,
- TimefilterContract,
-} from '../../../../plugins/data/public';
+import { IAggConfig, search, TimefilterContract } from '../../../../plugins/data/public';
import { Vis, VisParams } from '../types';
const { isDateHistogramBucketAggConfig } = search.aggs;
@@ -113,11 +108,9 @@ const getSchemas = (
'max_bucket',
].includes(agg.type.name);
- const format = fieldFormats.serialize(
- hasSubAgg
- ? agg.params.customMetric || agg.aggConfigs.getRequestAggById(agg.params.metricAgg)
- : agg
- );
+ const formatAgg = hasSubAgg
+ ? agg.params.customMetric || agg.aggConfigs.getRequestAggById(agg.params.metricAgg)
+ : agg;
const params: SchemaConfigParams = {};
@@ -130,7 +123,7 @@ const getSchemas = (
return {
accessor,
- format,
+ format: formatAgg.toSerializedFieldFormat(),
params,
label,
aggType: agg.type.name,
diff --git a/tsconfig.types.json b/tsconfig.types.json
index fd3624dd8e31b..2f5919e413e51 100644
--- a/tsconfig.types.json
+++ b/tsconfig.types.json
@@ -10,6 +10,8 @@
"include": [
"src/core/server/index.ts",
"src/core/public/index.ts",
+ "src/plugins/data/server/index.ts",
+ "src/plugins/data/public/index.ts",
"typings"
]
}
From 59011e9318d4e239ba762e620455c276bf76d8e2 Mon Sep 17 00:00:00 2001
From: Brandon Morelli
Date: Wed, 24 Jun 2020 11:01:43 -0700
Subject: [PATCH 05/82] [Obs] Update Observability landing page text (#69727)
(#69770)
---
x-pack/plugins/observability/public/pages/home/index.tsx | 4 ++--
x-pack/plugins/observability/public/pages/home/section.ts | 8 ++++----
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/x-pack/plugins/observability/public/pages/home/index.tsx b/x-pack/plugins/observability/public/pages/home/index.tsx
index 696361393ef82..91e7e2759b824 100644
--- a/x-pack/plugins/observability/public/pages/home/index.tsx
+++ b/x-pack/plugins/observability/public/pages/home/index.tsx
@@ -92,7 +92,7 @@ export const Home = () => {
{i18n.translate('xpack.observability.home.sectionTitle', {
- defaultMessage: 'Observability built on the Elastic Stack',
+ defaultMessage: 'Unified visibility across your entire ecosystem',
})}
@@ -100,7 +100,7 @@ export const Home = () => {
{i18n.translate('xpack.observability.home.sectionsubtitle', {
defaultMessage:
- 'Bring your logs, metrics, and APM traces together at scale in a single stack so you can monitor and react to events happening anywhere in your environment.',
+ 'Monitor, analyze, and react to events happening anywhere in your environment by bringing logs, metrics, and traces together at scale in a single stack.',
})}
diff --git a/x-pack/plugins/observability/public/pages/home/section.ts b/x-pack/plugins/observability/public/pages/home/section.ts
index a2b82c31bf2ab..d33571a16ccb7 100644
--- a/x-pack/plugins/observability/public/pages/home/section.ts
+++ b/x-pack/plugins/observability/public/pages/home/section.ts
@@ -23,7 +23,7 @@ export const appsSection: ISection[] = [
icon: 'logoLogging',
description: i18n.translate('xpack.observability.section.apps.logs.description', {
defaultMessage:
- 'The Elastic Stack (sometimes known as the ELK Stack) is the most popular open source logging platform.',
+ 'Centralize logs from any source. Search, tail, automate anomaly detection, and visualize trends so you can take action quicker.',
}),
},
{
@@ -34,7 +34,7 @@ export const appsSection: ISection[] = [
icon: 'logoAPM',
description: i18n.translate('xpack.observability.section.apps.apm.description', {
defaultMessage:
- 'See exactly where your application is spending time so you can quickly fix issues and feel good about the code you push.',
+ 'Trace transactions through a distributed architecture and map your services’ interactions to easily spot performance bottlenecks.',
}),
},
{
@@ -45,7 +45,7 @@ export const appsSection: ISection[] = [
icon: 'logoMetrics',
description: i18n.translate('xpack.observability.section.apps.metrics.description', {
defaultMessage:
- 'Already using the Elastic Stack for logs? Add metrics in just a few steps and correlate metrics and logs in one place.',
+ 'Analyze metrics from your infrastructure, apps, and services. Discover trends, forecast behavior, get alerts on anomalies, and more.',
}),
},
{
@@ -56,7 +56,7 @@ export const appsSection: ISection[] = [
icon: 'logoUptime',
description: i18n.translate('xpack.observability.section.apps.uptime.description', {
defaultMessage:
- 'React to availability issues across your apps and services before they affect users.',
+ 'Proactively monitor the availability of your sites and services. Receive alerts and resolve issues faster to optimize your users’ experience.',
}),
},
];
From 08648666856e44189fc1d7051729df6a779a284e Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Wed, 24 Jun 2020 20:06:48 +0200
Subject: [PATCH 06/82] [Lens] Stabilize filter popover (#69519) (#69821)
* stabilize filter popovwer
* remove text exclusion
---
x-pack/test/functional/apps/lens/smokescreen.ts | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts
index 181d41d77b4cb..b399c9e915e27 100644
--- a/x-pack/test/functional/apps/lens/smokescreen.ts
+++ b/x-pack/test/functional/apps/lens/smokescreen.ts
@@ -23,6 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
const dashboardAddPanel = getService('dashboardAddPanel');
const elasticChart = getService('elasticChart');
const browser = getService('browser');
+ const retry = getService('retry');
const testSubjects = getService('testSubjects');
const filterBar = getService('filterBar');
const listingTable = getService('listingTable');
@@ -93,7 +94,11 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
await dashboardAddPanel.closeAddPanel();
await PageObjects.lens.goToTimeRange();
await clickOnBarHistogram();
- await testSubjects.click('applyFiltersPopoverButton');
+
+ await retry.try(async () => {
+ await testSubjects.click('applyFiltersPopoverButton');
+ await testSubjects.missingOrFail('applyFiltersPopoverButton');
+ });
await assertExpectedChart();
await assertExpectedTimerange();
From 0ae2453536ff48e8bcfaa35286d3eb63279bc651 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Wed, 24 Jun 2020 14:21:38 -0400
Subject: [PATCH 07/82] [7.x] [IM] Move common step containers to shared
(#69713) (#69828)
---
.../components/shared/components/index.ts | 7 +++++-
.../shared/components/wizard_steps/index.ts | 8 ++++---
.../wizard_steps/step_aliases_container.tsx | 22 ++++++++++++++++++
.../wizard_steps}/step_mappings_container.tsx | 17 ++++++++------
.../wizard_steps/step_settings_container.tsx | 22 ++++++++++++++++++
.../shared/components/wizard_steps/types.ts | 13 +++++++++++
.../application/components/shared/index.ts | 7 +++---
.../components/template_form/steps/index.ts | 3 ---
.../steps/step_aliases_container.tsx | 23 -------------------
.../steps/step_settings_container.tsx | 23 -------------------
.../template_form/template_form.tsx | 18 +++++++--------
11 files changed, 90 insertions(+), 73 deletions(-)
create mode 100644 x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases_container.tsx
rename x-pack/plugins/index_management/public/application/components/{template_form/steps => shared/components/wizard_steps}/step_mappings_container.tsx (57%)
create mode 100644 x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings_container.tsx
create mode 100644 x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/types.ts
delete mode 100644 x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx
delete mode 100644 x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx
diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts
index e1700ad6a632d..b67a9c355e723 100644
--- a/x-pack/plugins/index_management/public/application/components/shared/components/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/index.ts
@@ -6,4 +6,9 @@
export { TabAliases, TabMappings, TabSettings } from './details_panel';
-export { StepAliases, StepMappings, StepSettings } from './wizard_steps';
+export {
+ StepAliasesContainer,
+ StepMappingsContainer,
+ StepSettingsContainer,
+ CommonWizardSteps,
+} from './wizard_steps';
diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts
index 90ce6227c09c8..ea554ca269d8b 100644
--- a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/index.ts
@@ -4,6 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { StepAliases } from './step_aliases';
-export { StepMappings } from './step_mappings';
-export { StepSettings } from './step_settings';
+export { StepAliasesContainer } from './step_aliases_container';
+export { StepMappingsContainer } from './step_mappings_container';
+export { StepSettingsContainer } from './step_settings_container';
+
+export { CommonWizardSteps } from './types';
diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases_container.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases_container.tsx
new file mode 100644
index 0000000000000..a5953ea00a106
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_aliases_container.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+
+import { Forms } from '../../../../../shared_imports';
+import { CommonWizardSteps } from './types';
+import { StepAliases } from './step_aliases';
+
+interface Props {
+ esDocsBase: string;
+}
+
+export const StepAliasesContainer: React.FunctionComponent = ({ esDocsBase }) => {
+ const { defaultValue, updateContent } = Forms.useContent('aliases');
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings_container.tsx
similarity index 57%
rename from x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx
rename to x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings_container.tsx
index 80c0d1d4df489..34e05d88c651d 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_mappings_container.tsx
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_mappings_container.tsx
@@ -5,20 +5,23 @@
*/
import React from 'react';
-import { Forms } from '../../../../shared_imports';
-import { documentationService } from '../../../services/documentation';
-import { StepMappings } from '../../shared';
-import { WizardContent } from '../template_form';
+import { Forms } from '../../../../../shared_imports';
+import { CommonWizardSteps } from './types';
+import { StepMappings } from './step_mappings';
-export const StepMappingsContainer = () => {
- const { defaultValue, updateContent, getData } = Forms.useContent('mappings');
+interface Props {
+ esDocsBase: string;
+}
+
+export const StepMappingsContainer: React.FunctionComponent = ({ esDocsBase }) => {
+ const { defaultValue, updateContent, getData } = Forms.useContent('mappings');
return (
);
};
diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings_container.tsx b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings_container.tsx
new file mode 100644
index 0000000000000..c540ddceb95c2
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/step_settings_container.tsx
@@ -0,0 +1,22 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import React from 'react';
+
+import { Forms } from '../../../../../shared_imports';
+import { CommonWizardSteps } from './types';
+import { StepSettings } from './step_settings';
+
+interface Props {
+ esDocsBase: string;
+}
+
+export const StepSettingsContainer = React.memo(({ esDocsBase }: Props) => {
+ const { defaultValue, updateContent } = Forms.useContent('settings');
+
+ return (
+
+ );
+});
diff --git a/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/types.ts b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/types.ts
new file mode 100644
index 0000000000000..f8088e2b6e058
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/components/shared/components/wizard_steps/types.ts
@@ -0,0 +1,13 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { Mappings, IndexSettings, Aliases } from '../../../../../../common';
+
+export interface CommonWizardSteps {
+ settings?: IndexSettings;
+ mappings?: Mappings;
+ aliases?: Aliases;
+}
diff --git a/x-pack/plugins/index_management/public/application/components/shared/index.ts b/x-pack/plugins/index_management/public/application/components/shared/index.ts
index 5ec1f71710270..897e86c99eca0 100644
--- a/x-pack/plugins/index_management/public/application/components/shared/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/shared/index.ts
@@ -8,7 +8,8 @@ export {
TabAliases,
TabMappings,
TabSettings,
- StepAliases,
- StepMappings,
- StepSettings,
+ StepAliasesContainer,
+ StepMappingsContainer,
+ StepSettingsContainer,
+ CommonWizardSteps,
} from './components';
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/index.ts b/x-pack/plugins/index_management/public/application/components/template_form/steps/index.ts
index 95d1222ad2cc9..b7e3e36e61814 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/index.ts
+++ b/x-pack/plugins/index_management/public/application/components/template_form/steps/index.ts
@@ -5,7 +5,4 @@
*/
export { StepLogisticsContainer } from './step_logistics_container';
-export { StepAliasesContainer } from './step_aliases_container';
-export { StepMappingsContainer } from './step_mappings_container';
-export { StepSettingsContainer } from './step_settings_container';
export { StepReviewContainer } from './step_review_container';
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx
deleted file mode 100644
index a0e0c59be6622..0000000000000
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_aliases_container.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import React from 'react';
-
-import { Forms } from '../../../../shared_imports';
-import { documentationService } from '../../../services/documentation';
-import { StepAliases } from '../../shared';
-import { WizardContent } from '../template_form';
-
-export const StepAliasesContainer = () => {
- const { defaultValue, updateContent } = Forms.useContent('aliases');
-
- return (
-
- );
-};
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx b/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx
deleted file mode 100644
index b79c6804d382b..0000000000000
--- a/x-pack/plugins/index_management/public/application/components/template_form/steps/step_settings_container.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import React from 'react';
-
-import { Forms } from '../../../../shared_imports';
-import { documentationService } from '../../../services/documentation';
-import { StepSettings } from '../../shared';
-import { WizardContent } from '../template_form';
-
-export const StepSettingsContainer = React.memo(() => {
- const { defaultValue, updateContent } = Forms.useContent('settings');
-
- return (
-
- );
-});
diff --git a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
index 9e6d49faac563..8a2c991aea8d0 100644
--- a/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
+++ b/x-pack/plugins/index_management/public/application/components/template_form/template_form.tsx
@@ -11,13 +11,14 @@ import { EuiSpacer } from '@elastic/eui';
import { TemplateDeserialized, CREATE_LEGACY_TEMPLATE_BY_DEFAULT } from '../../../../common';
import { serializers, Forms } from '../../../shared_imports';
import { SectionError } from '../section_error';
+import { StepLogisticsContainer, StepReviewContainer } from './steps';
import {
- StepLogisticsContainer,
+ CommonWizardSteps,
StepSettingsContainer,
StepMappingsContainer,
StepAliasesContainer,
- StepReviewContainer,
-} from './steps';
+} from '../shared';
+import { documentationService } from '../../services/documentation';
const { stripEmptyFields } = serializers;
const { FormWizard, FormWizardStep } = Forms;
@@ -31,11 +32,8 @@ interface Props {
isEditing?: boolean;
}
-export interface WizardContent {
+export interface WizardContent extends CommonWizardSteps {
logistics: Omit;
- settings: TemplateDeserialized['template']['settings'];
- mappings: TemplateDeserialized['template']['mappings'];
- aliases: TemplateDeserialized['template']['aliases'];
}
export type WizardSection = keyof WizardContent | 'review';
@@ -183,15 +181,15 @@ export const TemplateForm = ({
-
+
-
+
-
+
From 5bed09cd3251e1f464be9c7784e8d524d9ab4d8a Mon Sep 17 00:00:00 2001
From: Ryland Herrick
Date: Wed, 24 Jun 2020 13:29:43 -0500
Subject: [PATCH 08/82] Add lists plugin to optimized security_solution TS
config (#69705) (#69823)
As security_solution continues to integrate with lists, the absents of
these types will lead to lots of implicit anys and false positives.
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
.../security_solution/scripts/optimize_tsconfig/tsconfig.json | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/security_solution/scripts/optimize_tsconfig/tsconfig.json b/x-pack/plugins/security_solution/scripts/optimize_tsconfig/tsconfig.json
index dcce9746086e0..ea7a11b89dab2 100644
--- a/x-pack/plugins/security_solution/scripts/optimize_tsconfig/tsconfig.json
+++ b/x-pack/plugins/security_solution/scripts/optimize_tsconfig/tsconfig.json
@@ -1,6 +1,7 @@
{
"include": [
"typings/**/*",
+ "plugins/lists/**/*",
"plugins/security_solution/**/*",
"plugins/apm/typings/numeral.d.ts",
"plugins/canvas/types/webpack.d.ts",
From 8935d79bdad42f42b433294136c466811eae480c Mon Sep 17 00:00:00 2001
From: Ryland Herrick
Date: Wed, 24 Jun 2020 13:31:39 -0500
Subject: [PATCH 09/82] [Security][Network] Exclude glob-only (*) Index Pattern
from map layers (#69736) (#69824)
* Exclude glob-only (*) index pattern from map layers
This pattern is a special case that our map should ignore, as including
it causes all indexes to be queried.
* Ignore CCS glob pattern in our embedded map
Users may have this pattern for cross-cluster search, and it should
similarly be excluded when matching Security indexes.
---
.../components/embeddables/__mocks__/mock.ts | 9 +++++++++
.../embeddables/embedded_map_helpers.test.tsx | 13 +++++++++++--
.../components/embeddables/embedded_map_helpers.tsx | 13 ++++++++++---
3 files changed, 30 insertions(+), 5 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts b/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts
index bc1de567b60ae..6f8c3e1123854 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/__mocks__/mock.ts
@@ -475,3 +475,12 @@ export const mockGlobIndexPattern: IndexPatternSavedObject = {
title: '*',
},
};
+
+export const mockCCSGlobIndexPattern: IndexPatternSavedObject = {
+ id: '*:*',
+ type: 'index-pattern',
+ _version: 'abc',
+ attributes: {
+ title: '*:*',
+ },
+};
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.test.tsx
index d42ac919e9af0..50170f4f6ae9e 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.test.tsx
@@ -14,6 +14,7 @@ import {
mockAuditbeatIndexPattern,
mockFilebeatIndexPattern,
mockGlobIndexPattern,
+ mockCCSGlobIndexPattern,
} from './__mocks__/mock';
const mockEmbeddable = embeddablePluginMock.createStartContract();
@@ -106,12 +107,20 @@ describe('embedded_map_helpers', () => {
]);
});
- test('finds glob-only index patterns ', () => {
+ test('excludes glob-only index patterns', () => {
const matchingIndexPatterns = findMatchingIndexPatterns({
kibanaIndexPatterns: [mockGlobIndexPattern, mockFilebeatIndexPattern],
siemDefaultIndices,
});
- expect(matchingIndexPatterns).toEqual([mockGlobIndexPattern, mockFilebeatIndexPattern]);
+ expect(matchingIndexPatterns).toEqual([mockFilebeatIndexPattern]);
+ });
+
+ test('excludes glob-only CCS index patterns', () => {
+ const matchingIndexPatterns = findMatchingIndexPatterns({
+ kibanaIndexPatterns: [mockCCSGlobIndexPattern, mockFilebeatIndexPattern],
+ siemDefaultIndices,
+ });
+ expect(matchingIndexPatterns).toEqual([mockFilebeatIndexPattern]);
});
});
});
diff --git a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx
index e50dcd7a8c8d8..b0f8e2cc02403 100644
--- a/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/embeddables/embedded_map_helpers.tsx
@@ -128,6 +128,9 @@ export const createEmbeddable = async (
return embeddableObject;
};
+// These patterns are overly greedy and must be excluded when matching against Security indexes.
+const ignoredIndexPatterns = ['*', '*:*'];
+
/**
* Returns kibanaIndexPatterns that wildcard match at least one of siemDefaultIndices
*
@@ -142,9 +145,13 @@ export const findMatchingIndexPatterns = ({
siemDefaultIndices: string[];
}): IndexPatternSavedObject[] => {
try {
- return kibanaIndexPatterns.filter((kip) =>
- siemDefaultIndices.some((sdi) => minimatch(sdi, kip.attributes.title))
- );
+ return kibanaIndexPatterns.filter((kip) => {
+ const pattern = kip.attributes.title;
+ return (
+ !ignoredIndexPatterns.includes(pattern) &&
+ siemDefaultIndices.some((sdi) => minimatch(sdi, pattern))
+ );
+ });
} catch {
return [];
}
From eb94c7fbf92002c15190dbceec184ce0daf2e5ef Mon Sep 17 00:00:00 2001
From: Luke Elmers
Date: Wed, 24 Jun 2020 12:45:45 -0600
Subject: [PATCH 10/82] [data.search.aggs] Move agg-specific field formats to
search service (#69586) (#69825)
---
.../public/field_formats/utils/deserialize.ts | 107 ++++------------
.../aggs/utils/get_format_with_aggs.test.ts | 99 +++++++++++++++
.../search/aggs/utils/get_format_with_aggs.ts | 116 ++++++++++++++++++
.../data/public/search/aggs/utils/index.ts | 1 +
.../expressions/common/types/common.ts | 2 +-
5 files changed, 238 insertions(+), 87 deletions(-)
create mode 100644 src/plugins/data/public/search/aggs/utils/get_format_with_aggs.test.ts
create mode 100644 src/plugins/data/public/search/aggs/utils/get_format_with_aggs.ts
diff --git a/src/plugins/data/public/field_formats/utils/deserialize.ts b/src/plugins/data/public/field_formats/utils/deserialize.ts
index d9c713c8b1eb4..26baa5fdeb1e4 100644
--- a/src/plugins/data/public/field_formats/utils/deserialize.ts
+++ b/src/plugins/data/public/field_formats/utils/deserialize.ts
@@ -18,107 +18,42 @@
*/
import { identity } from 'lodash';
-import { i18n } from '@kbn/i18n';
-import { convertDateRangeToString, DateRangeKey } from '../../search/aggs/buckets/lib/date_range';
-import { convertIPRangeToString, IpRangeKey } from '../../search/aggs/buckets/lib/ip_range';
+
import { SerializedFieldFormat } from '../../../../expressions/common/types';
-import { FieldFormatId, FieldFormatsContentType, IFieldFormat } from '../..';
+
import { FieldFormat } from '../../../common';
-import { DataPublicPluginStart } from '../../../public';
-import { getUiSettings } from '../../../public/services';
import { FormatFactory } from '../../../common/field_formats/utils';
-
-interface TermsFieldFormatParams {
- otherBucketLabel: string;
- missingBucketLabel: string;
- id: string;
-}
-
-function isTermsFieldFormat(
- serializedFieldFormat: SerializedFieldFormat
-): serializedFieldFormat is SerializedFieldFormat {
- return serializedFieldFormat.id === 'terms';
-}
+import { DataPublicPluginStart, IFieldFormat } from '../../../public';
+import { getUiSettings } from '../../../public/services';
+import { getFormatWithAggs } from '../../search/aggs/utils';
const getConfig = (key: string, defaultOverride?: any): any =>
getUiSettings().get(key, defaultOverride);
const DefaultFieldFormat = FieldFormat.from(identity);
-const getFieldFormat = (
- fieldFormatsService: DataPublicPluginStart['fieldFormats'],
- id?: FieldFormatId,
- params: object = {}
-): IFieldFormat => {
- if (id) {
- const Format = fieldFormatsService.getType(id);
-
- if (Format) {
- return new Format(params, getConfig);
- }
- }
-
- return new DefaultFieldFormat();
-};
-
export const deserializeFieldFormat: FormatFactory = function (
this: DataPublicPluginStart['fieldFormats'],
- mapping?: SerializedFieldFormat
+ serializedFieldFormat?: SerializedFieldFormat
) {
- if (!mapping) {
+ if (!serializedFieldFormat) {
return new DefaultFieldFormat();
}
- const { id } = mapping;
- if (id === 'range') {
- const RangeFormat = FieldFormat.from((range: any) => {
- const nestedFormatter = mapping.params as SerializedFieldFormat;
- const format = getFieldFormat(this, nestedFormatter.id, nestedFormatter.params);
- const gte = '\u2265';
- const lt = '\u003c';
- return i18n.translate('data.aggTypes.buckets.ranges.rangesFormatMessage', {
- defaultMessage: '{gte} {from} and {lt} {to}',
- values: {
- gte,
- from: format.convert(range.gte),
- lt,
- to: format.convert(range.lt),
- },
- });
- });
- return new RangeFormat();
- } else if (id === 'date_range') {
- const nestedFormatter = mapping.params as SerializedFieldFormat;
- const DateRangeFormat = FieldFormat.from((range: DateRangeKey) => {
- const format = getFieldFormat(this, nestedFormatter.id, nestedFormatter.params);
- return convertDateRangeToString(range, format.convert.bind(format));
- });
- return new DateRangeFormat();
- } else if (id === 'ip_range') {
- const nestedFormatter = mapping.params as SerializedFieldFormat;
- const IpRangeFormat = FieldFormat.from((range: IpRangeKey) => {
- const format = getFieldFormat(this, nestedFormatter.id, nestedFormatter.params);
- return convertIPRangeToString(range, format.convert.bind(format));
- });
- return new IpRangeFormat();
- } else if (isTermsFieldFormat(mapping) && mapping.params) {
- const { params } = mapping;
- const convert = (val: string, type: FieldFormatsContentType) => {
- const format = getFieldFormat(this, params.id, mapping.params);
- if (val === '__other__') {
- return params.otherBucketLabel;
- }
- if (val === '__missing__') {
- return params.missingBucketLabel;
+ const getFormat = (mapping: SerializedFieldFormat): IFieldFormat => {
+ const { id, params = {} } = mapping;
+ if (id) {
+ const Format = this.getType(id);
+
+ if (Format) {
+ return new Format(params, getConfig);
}
+ }
+
+ return new DefaultFieldFormat();
+ };
- return format.convert(val, type);
- };
+ // decorate getFormat to handle custom types created by aggs
+ const getFieldFormat = getFormatWithAggs(getFormat);
- return {
- convert,
- getConverterFor: (type: FieldFormatsContentType) => (val: string) => convert(val, type),
- } as IFieldFormat;
- } else {
- return getFieldFormat(this, id, mapping.params);
- }
+ return getFieldFormat(serializedFieldFormat);
};
diff --git a/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.test.ts b/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.test.ts
new file mode 100644
index 0000000000000..3b440bc50c93b
--- /dev/null
+++ b/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.test.ts
@@ -0,0 +1,99 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { identity } from 'lodash';
+
+import { SerializedFieldFormat } from '../../../../../expressions/common/types';
+import { FieldFormat } from '../../../../common';
+import { IFieldFormat } from '../../../../public';
+
+import { getFormatWithAggs } from './get_format_with_aggs';
+
+describe('getFormatWithAggs', () => {
+ let getFormat: jest.MockedFunction<(mapping: SerializedFieldFormat) => IFieldFormat>;
+
+ beforeEach(() => {
+ getFormat = jest.fn().mockImplementation(() => {
+ const DefaultFieldFormat = FieldFormat.from(identity);
+ return new DefaultFieldFormat();
+ });
+ });
+
+ test('calls provided getFormat if no matching aggs exist', () => {
+ const mapping = { id: 'foo', params: {} };
+ const getFieldFormat = getFormatWithAggs(getFormat);
+ getFieldFormat(mapping);
+
+ expect(getFormat).toHaveBeenCalledTimes(1);
+ expect(getFormat).toHaveBeenCalledWith(mapping);
+ });
+
+ test('creates custom format for date_range', () => {
+ const mapping = { id: 'date_range', params: {} };
+ const getFieldFormat = getFormatWithAggs(getFormat);
+ const format = getFieldFormat(mapping);
+
+ expect(format.convert({ from: '2020-05-01', to: '2020-06-01' })).toBe(
+ '2020-05-01 to 2020-06-01'
+ );
+ expect(format.convert({ to: '2020-06-01' })).toBe('Before 2020-06-01');
+ expect(format.convert({ from: '2020-06-01' })).toBe('After 2020-06-01');
+ expect(getFormat).toHaveBeenCalledTimes(3);
+ });
+
+ test('creates custom format for ip_range', () => {
+ const mapping = { id: 'ip_range', params: {} };
+ const getFieldFormat = getFormatWithAggs(getFormat);
+ const format = getFieldFormat(mapping);
+
+ expect(format.convert({ type: 'range', from: '10.0.0.1', to: '10.0.0.10' })).toBe(
+ '10.0.0.1 to 10.0.0.10'
+ );
+ expect(format.convert({ type: 'range', to: '10.0.0.10' })).toBe('-Infinity to 10.0.0.10');
+ expect(format.convert({ type: 'range', from: '10.0.0.10' })).toBe('10.0.0.10 to Infinity');
+ format.convert({ type: 'mask', mask: '10.0.0.1/24' });
+ expect(getFormat).toHaveBeenCalledTimes(4);
+ });
+
+ test('creates custom format for range', () => {
+ const mapping = { id: 'range', params: {} };
+ const getFieldFormat = getFormatWithAggs(getFormat);
+ const format = getFieldFormat(mapping);
+
+ expect(format.convert({ gte: 1, lt: 20 })).toBe('≥ 1 and < 20');
+ expect(getFormat).toHaveBeenCalledTimes(1);
+ });
+
+ test('creates custom format for terms', () => {
+ const mapping = {
+ id: 'terms',
+ params: {
+ otherBucketLabel: 'other bucket',
+ missingBucketLabel: 'missing bucket',
+ },
+ };
+ const getFieldFormat = getFormatWithAggs(getFormat);
+ const format = getFieldFormat(mapping);
+
+ expect(format.convert('machine.os.keyword')).toBe('machine.os.keyword');
+ expect(format.convert('__other__')).toBe(mapping.params.otherBucketLabel);
+ expect(format.convert('__missing__')).toBe(mapping.params.missingBucketLabel);
+ expect(getFormat).toHaveBeenCalledTimes(3);
+ });
+});
diff --git a/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.ts b/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.ts
new file mode 100644
index 0000000000000..e0db249c7cf86
--- /dev/null
+++ b/src/plugins/data/public/search/aggs/utils/get_format_with_aggs.ts
@@ -0,0 +1,116 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+import { SerializedFieldFormat } from '../../../../../expressions/common/types';
+import { FieldFormat } from '../../../../common';
+import { FieldFormatsContentType, IFieldFormat } from '../../../../public';
+import { convertDateRangeToString, DateRangeKey } from '../buckets/lib/date_range';
+import { convertIPRangeToString, IpRangeKey } from '../buckets/lib/ip_range';
+
+type GetFieldFormat = (mapping: SerializedFieldFormat) => IFieldFormat;
+
+/**
+ * Certain aggs have custom field formats that are not part of the field formats
+ * registry. This function will take the `getFormat` function which is used inside
+ * `deserializeFieldFormat` and decorate it with the additional custom formats
+ * that the field formats service doesn't know anything about.
+ *
+ * This function is internal to the data plugin, and only exists for use inside
+ * the field formats service.
+ *
+ * @internal
+ */
+export function getFormatWithAggs(getFieldFormat: GetFieldFormat): GetFieldFormat {
+ return (mapping) => {
+ const { id, params = {} } = mapping;
+
+ const customFormats: Record IFieldFormat> = {
+ range: () => {
+ const RangeFormat = FieldFormat.from((range: any) => {
+ const nestedFormatter = params as SerializedFieldFormat;
+ const format = getFieldFormat({
+ id: nestedFormatter.id,
+ params: nestedFormatter.params,
+ });
+ const gte = '\u2265';
+ const lt = '\u003c';
+ return i18n.translate('data.aggTypes.buckets.ranges.rangesFormatMessage', {
+ defaultMessage: '{gte} {from} and {lt} {to}',
+ values: {
+ gte,
+ from: format.convert(range.gte),
+ lt,
+ to: format.convert(range.lt),
+ },
+ });
+ });
+ return new RangeFormat();
+ },
+ date_range: () => {
+ const nestedFormatter = params as SerializedFieldFormat;
+ const DateRangeFormat = FieldFormat.from((range: DateRangeKey) => {
+ const format = getFieldFormat({
+ id: nestedFormatter.id,
+ params: nestedFormatter.params,
+ });
+ return convertDateRangeToString(range, format.convert.bind(format));
+ });
+ return new DateRangeFormat();
+ },
+ ip_range: () => {
+ const nestedFormatter = params as SerializedFieldFormat;
+ const IpRangeFormat = FieldFormat.from((range: IpRangeKey) => {
+ const format = getFieldFormat({
+ id: nestedFormatter.id,
+ params: nestedFormatter.params,
+ });
+ return convertIPRangeToString(range, format.convert.bind(format));
+ });
+ return new IpRangeFormat();
+ },
+ terms: () => {
+ const convert = (val: string, type: FieldFormatsContentType) => {
+ const format = getFieldFormat({ id: params.id, params });
+
+ if (val === '__other__') {
+ return params.otherBucketLabel;
+ }
+ if (val === '__missing__') {
+ return params.missingBucketLabel;
+ }
+
+ return format.convert(val, type);
+ };
+
+ return {
+ convert,
+ getConverterFor: (type: FieldFormatsContentType) => (val: string) => convert(val, type),
+ } as IFieldFormat;
+ },
+ };
+
+ if (!id || !(id in customFormats)) {
+ return getFieldFormat(mapping);
+ }
+
+ return customFormats[id]();
+ };
+}
diff --git a/src/plugins/data/public/search/aggs/utils/index.ts b/src/plugins/data/public/search/aggs/utils/index.ts
index 169d872b17d3a..5a889ee9ead9d 100644
--- a/src/plugins/data/public/search/aggs/utils/index.ts
+++ b/src/plugins/data/public/search/aggs/utils/index.ts
@@ -18,5 +18,6 @@
*/
export * from './calculate_auto_time_expression';
+export * from './get_format_with_aggs';
export * from './prop_filter';
export * from './to_angular_json';
diff --git a/src/plugins/expressions/common/types/common.ts b/src/plugins/expressions/common/types/common.ts
index f532f9708940e..040979e4264b5 100644
--- a/src/plugins/expressions/common/types/common.ts
+++ b/src/plugins/expressions/common/types/common.ts
@@ -61,7 +61,7 @@ export type UnmappedTypeStrings = 'date' | 'filter';
* Is used to carry information about how to format data in
* a data table as part of the column definition.
*/
-export interface SerializedFieldFormat {
+export interface SerializedFieldFormat> {
id?: string;
params?: TParams;
}
From 17162394ffaf96c60e194feb8f614ad633c5e9c9 Mon Sep 17 00:00:00 2001
From: Melissa Alvarez
Date: Wed, 24 Jun 2020 16:20:53 -0400
Subject: [PATCH 11/82] [ML] DF Analytics Creation: add progress indicator
(#69583) (#69837)
* add progress indicator to creation wizard page
* only show progress bar if job is started immediately
* add title and switch to timeout
* fix progress check
* clean up interval on unmount
* fix types
* clear interval if stats undefined. show progress if job created
---
.../components/create_step/create_step.tsx | 5 +
.../components/create_step/progress_stats.tsx | 110 ++++++++++++++++++
2 files changed, 115 insertions(+)
create mode 100644 x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/progress_stats.tsx
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx
index 8d51848a25f50..0d1690cf17946 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/create_step.tsx
@@ -19,6 +19,7 @@ import { CreateAnalyticsFormProps } from '../../../analytics_management/hooks/us
import { Messages } from '../shared';
import { ANALYTICS_STEPS } from '../../page';
import { BackToListPanel } from '../back_to_list_panel';
+import { ProgressStats } from './progress_stats';
interface Props extends CreateAnalyticsFormProps {
step: ANALYTICS_STEPS;
@@ -27,8 +28,10 @@ interface Props extends CreateAnalyticsFormProps {
export const CreateStep: FC = ({ actions, state, step }) => {
const { createAnalyticsJob, startAnalyticsJob } = actions;
const { isAdvancedEditorValidJson, isJobCreated, isJobStarted, isValid, requestMessages } = state;
+ const { jobId } = state.form;
const [checked, setChecked] = useState(true);
+ const [showProgress, setShowProgress] = useState(false);
if (step !== ANALYTICS_STEPS.CREATE) return null;
@@ -36,6 +39,7 @@ export const CreateStep: FC = ({ actions, state, step }) => {
await createAnalyticsJob();
if (checked) {
+ setShowProgress(true);
startAnalyticsJob();
}
};
@@ -82,6 +86,7 @@ export const CreateStep: FC = ({ actions, state, step }) => {
)}
+ {isJobCreated === true && showProgress && }
{isJobCreated === true && }
);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/progress_stats.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/progress_stats.tsx
new file mode 100644
index 0000000000000..8cee63d3c4c84
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/create_step/progress_stats.tsx
@@ -0,0 +1,110 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, useState, useEffect } from 'react';
+import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSpacer, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { useMlKibana } from '../../../../../contexts/kibana';
+import { getDataFrameAnalyticsProgressPhase } from '../../../analytics_management/components/analytics_list/common';
+import { isGetDataFrameAnalyticsStatsResponseOk } from '../../../analytics_management/services/analytics_service/get_analytics';
+import { ml } from '../../../../../services/ml_api_service';
+import { DataFrameAnalyticsId } from '../../../../common/analytics';
+
+export const PROGRESS_REFRESH_INTERVAL_MS = 1000;
+
+export const ProgressStats: FC<{ jobId: DataFrameAnalyticsId }> = ({ jobId }) => {
+ const [initialized, setInitialized] = useState(false);
+ const [currentProgress, setCurrentProgress] = useState<
+ | {
+ currentPhase: number;
+ progress: number;
+ totalPhases: number;
+ }
+ | undefined
+ >(undefined);
+
+ const {
+ services: { notifications },
+ } = useMlKibana();
+
+ useEffect(() => {
+ setInitialized(true);
+ }, []);
+
+ useEffect(() => {
+ const interval = setInterval(async () => {
+ try {
+ const analyticsStats = await ml.dataFrameAnalytics.getDataFrameAnalyticsStats(jobId);
+ const jobStats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats)
+ ? analyticsStats.data_frame_analytics[0]
+ : undefined;
+
+ if (jobStats !== undefined) {
+ const progressStats = getDataFrameAnalyticsProgressPhase(jobStats);
+ setCurrentProgress(progressStats);
+ if (
+ progressStats.currentPhase === progressStats.totalPhases &&
+ progressStats.progress === 100
+ ) {
+ clearInterval(interval);
+ }
+ } else {
+ clearInterval(interval);
+ }
+ } catch (e) {
+ notifications.toasts.addDanger(
+ i18n.translate('xpack.ml.dataframe.analytics.create.analyticsProgressErrorMessage', {
+ defaultMessage: 'An error occurred getting progress stats for analytics job {jobId}',
+ values: { jobId },
+ })
+ );
+ clearInterval(interval);
+ }
+ }, PROGRESS_REFRESH_INTERVAL_MS);
+
+ return () => clearInterval(interval);
+ }, [initialized]);
+
+ if (currentProgress === undefined) return null;
+
+ return (
+ <>
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.analyticsProgressTitle', {
+ defaultMessage: 'Progress',
+ })}
+
+
+
+
+
+
+
+ {i18n.translate('xpack.ml.dataframe.analytics.create.analyticsProgressPhaseTitle', {
+ defaultMessage: 'Phase',
+ })}{' '}
+ {currentProgress.currentPhase}/{currentProgress.totalPhases}
+
+
+
+
+
+
+
+ {`${currentProgress.progress}%`}
+
+
+ >
+ );
+};
From 7371ad8b012ddf6896b2d5da00a144c00ac6013f Mon Sep 17 00:00:00 2001
From: Aaron Caldwell
Date: Wed, 24 Jun 2020 14:22:08 -0600
Subject: [PATCH 12/82] [7.x] [Maps] New mappings: maps-telemetry -> maps
(#69816) (#69833)
---
x-pack/plugins/maps/server/saved_objects/maps_telemetry.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/maps/server/saved_objects/maps_telemetry.ts b/x-pack/plugins/maps/server/saved_objects/maps_telemetry.ts
index 2512bf3094bcf..ad0b17af36dda 100644
--- a/x-pack/plugins/maps/server/saved_objects/maps_telemetry.ts
+++ b/x-pack/plugins/maps/server/saved_objects/maps_telemetry.ts
@@ -6,7 +6,7 @@
import { SavedObjectsType } from 'src/core/server';
export const mapsTelemetrySavedObjects: SavedObjectsType = {
- name: 'maps-telemetry',
+ name: 'maps',
hidden: false,
namespaceType: 'agnostic',
mappings: {
From b0d97592f6ae637f112f68323d04348caced03ba Mon Sep 17 00:00:00 2001
From: Oliver Gupte
Date: Wed, 24 Jun 2020 15:30:48 -0700
Subject: [PATCH 13/82] [APM] Pulls legacy ML code from service maps and
integrations (#69779) (#69852)
* Pulls out existing ML integration from the service maps
* - removes ML job creation flyout in integrations menu on the service details UI
- removes ML searches and transforms in the transaction charts API
- removes unused shared functions and types related to the legacy ML integration
* removes unused translations for APM anomaly detection
* Adds tags to TODOs for easy searching later
# Conflicts:
# x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_bucket_span_response.ts
---
.../apm/common/ml_job_constants.test.ts | 38 +--
x-pack/plugins/apm/common/ml_job_constants.ts | 19 --
x-pack/plugins/apm/common/service_map.ts | 10 -
.../TransactionSelect.tsx | 56 ----
.../MachineLearningFlyout/index.tsx | 167 ----------
.../MachineLearningFlyout/view.tsx | 264 ---------------
.../ServiceIntegrations/index.tsx | 107 ++-----
.../app/ServiceMap/Popover/Contents.tsx | 9 +-
.../ServiceMap/Popover/anomaly_detection.tsx | 157 ---------
.../app/TransactionOverview/index.tsx | 18 +-
.../MachineLearningLinks/MLJobLink.test.tsx | 15 -
.../Links/MachineLearningLinks/MLJobLink.tsx | 18 +-
.../shared/charts/TransactionCharts/index.tsx | 11 +-
x-pack/plugins/apm/public/services/rest/ml.ts | 123 -------
.../service_map/get_service_anomalies.test.ts | 40 ---
.../lib/service_map/get_service_anomalies.ts | 166 ----------
.../server/lib/service_map/get_service_map.ts | 19 +-
.../server/lib/service_map/ml_helpers.test.ts | 76 -----
.../apm/server/lib/service_map/ml_helpers.ts | 67 ----
.../transform_service_map_responses.test.ts | 7 -
.../transform_service_map_responses.ts | 24 +-
.../__snapshots__/fetcher.test.ts.snap | 68 ----
.../__snapshots__/index.test.ts.snap | 38 ---
.../__snapshots__/transform.test.ts.snap | 33 --
.../charts/get_anomaly_data/fetcher.test.ts | 76 -----
.../charts/get_anomaly_data/fetcher.ts | 90 ------
.../get_anomaly_data/get_ml_bucket_size.ts | 65 ----
.../charts/get_anomaly_data/index.test.ts | 83 -----
.../charts/get_anomaly_data/index.ts | 39 +--
.../mock_responses/ml_anomaly_response.ts | 127 --------
.../mock_responses/ml_bucket_span_response.ts | 32 --
.../charts/get_anomaly_data/transform.test.ts | 303 ------------------
.../charts/get_anomaly_data/transform.ts | 126 --------
.../translations/translations/ja-JP.json | 20 --
.../translations/translations/zh-CN.json | 20 --
35 files changed, 57 insertions(+), 2474 deletions(-)
delete mode 100644 x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/TransactionSelect.tsx
delete mode 100644 x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/index.tsx
delete mode 100644 x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/view.tsx
delete mode 100644 x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx
delete mode 100644 x-pack/plugins/apm/public/services/rest/ml.ts
delete mode 100644 x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.test.ts
delete mode 100644 x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts
delete mode 100644 x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts
delete mode 100644 x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/fetcher.test.ts.snap
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/index.test.ts.snap
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/transform.test.ts.snap
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.test.ts
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_anomaly_response.ts
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_bucket_span_response.ts
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts
delete mode 100644 x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts
diff --git a/x-pack/plugins/apm/common/ml_job_constants.test.ts b/x-pack/plugins/apm/common/ml_job_constants.test.ts
index 45bb7133e852e..96e3ba826d201 100644
--- a/x-pack/plugins/apm/common/ml_job_constants.test.ts
+++ b/x-pack/plugins/apm/common/ml_job_constants.test.ts
@@ -4,45 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import {
- getMlJobId,
- getMlPrefix,
- getMlJobServiceName,
- getSeverity,
- severity,
-} from './ml_job_constants';
+import { getSeverity, severity } from './ml_job_constants';
describe('ml_job_constants', () => {
- it('getMlPrefix', () => {
- expect(getMlPrefix('myServiceName')).toBe('myservicename-');
- expect(getMlPrefix('myServiceName', 'myTransactionType')).toBe(
- 'myservicename-mytransactiontype-'
- );
- });
-
- it('getMlJobId', () => {
- expect(getMlJobId('myServiceName')).toBe(
- 'myservicename-high_mean_response_time'
- );
- expect(getMlJobId('myServiceName', 'myTransactionType')).toBe(
- 'myservicename-mytransactiontype-high_mean_response_time'
- );
- expect(getMlJobId('my service name')).toBe(
- 'my_service_name-high_mean_response_time'
- );
- expect(getMlJobId('my service name', 'my transaction type')).toBe(
- 'my_service_name-my_transaction_type-high_mean_response_time'
- );
- });
-
- describe('getMlJobServiceName', () => {
- it('extracts the service name from a job id', () => {
- expect(
- getMlJobServiceName('opbeans-node-request-high_mean_response_time')
- ).toEqual('opbeans-node');
- });
- });
-
describe('getSeverity', () => {
describe('when score is undefined', () => {
it('returns undefined', () => {
diff --git a/x-pack/plugins/apm/common/ml_job_constants.ts b/x-pack/plugins/apm/common/ml_job_constants.ts
index f9b0119d8a107..b8c2546bd0c84 100644
--- a/x-pack/plugins/apm/common/ml_job_constants.ts
+++ b/x-pack/plugins/apm/common/ml_job_constants.ts
@@ -11,25 +11,6 @@ export enum severity {
warning = 'warning',
}
-export const APM_ML_JOB_GROUP_NAME = 'apm';
-
-export function getMlPrefix(serviceName: string, transactionType?: string) {
- const maybeTransactionType = transactionType ? `${transactionType}-` : '';
- return encodeForMlApi(`${serviceName}-${maybeTransactionType}`);
-}
-
-export function getMlJobId(serviceName: string, transactionType?: string) {
- return `${getMlPrefix(serviceName, transactionType)}high_mean_response_time`;
-}
-
-export function getMlJobServiceName(jobId: string) {
- return jobId.split('-').slice(0, -2).join('-');
-}
-
-export function encodeForMlApi(value: string) {
- return value.replace(/\s+/g, '_').toLowerCase();
-}
-
export function getSeverity(score?: number) {
if (typeof score !== 'number') {
return undefined;
diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts
index 7d7a7811eeba2..43f3585d0ebb2 100644
--- a/x-pack/plugins/apm/common/service_map.ts
+++ b/x-pack/plugins/apm/common/service_map.ts
@@ -34,16 +34,6 @@ export interface Connection {
destination: ConnectionNode;
}
-export interface ServiceAnomaly {
- anomaly_score: number;
- anomaly_severity: string;
- actual_value: number;
- typical_value: number;
- ml_job_id: string;
-}
-
-export type ServiceNode = ConnectionNode & Partial;
-
export interface ServiceNodeMetrics {
avgMemoryUsage: number | null;
avgCpuUsage: number | null;
diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/TransactionSelect.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/TransactionSelect.tsx
deleted file mode 100644
index 42f7246b6ea35..0000000000000
--- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/TransactionSelect.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiFormRow,
- EuiSuperSelect,
- EuiText,
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-
-interface TransactionSelectProps {
- transactionTypes: string[];
- onChange: (value: string) => void;
- selectedTransactionType: string;
-}
-
-export function TransactionSelect({
- transactionTypes,
- onChange,
- selectedTransactionType,
-}: TransactionSelectProps) {
- return (
-
- {
- return {
- value: transactionType,
- inputDisplay: transactionType,
- dropdownDisplay: (
-
-
- {transactionType}
-
-
- ),
- };
- })}
- />
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/index.tsx
deleted file mode 100644
index 91778b2940c6b..0000000000000
--- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/index.tsx
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { i18n } from '@kbn/i18n';
-import React, { Component } from 'react';
-import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_react/public';
-import { startMLJob, MLError } from '../../../../../services/rest/ml';
-import { IUrlParams } from '../../../../../context/UrlParamsContext/types';
-import { MLJobLink } from '../../../../shared/Links/MachineLearningLinks/MLJobLink';
-import { MachineLearningFlyoutView } from './view';
-import { ApmPluginContext } from '../../../../../context/ApmPluginContext';
-
-interface Props {
- isOpen: boolean;
- onClose: () => void;
- urlParams: IUrlParams;
-}
-
-interface State {
- isCreatingJob: boolean;
-}
-
-export class MachineLearningFlyout extends Component {
- static contextType = ApmPluginContext;
-
- public state: State = {
- isCreatingJob: false,
- };
-
- public onClickCreate = async ({
- transactionType,
- }: {
- transactionType: string;
- }) => {
- this.setState({ isCreatingJob: true });
- try {
- const { http } = this.context.core;
- const { serviceName } = this.props.urlParams;
- if (!serviceName) {
- throw new Error('Service name is required to create this ML job');
- }
- const res = await startMLJob({ http, serviceName, transactionType });
- const didSucceed = res.datafeeds[0].success && res.jobs[0].success;
- if (!didSucceed) {
- throw new Error('Creating ML job failed');
- }
- this.addSuccessToast({ transactionType });
- } catch (e) {
- this.addErrorToast(e as MLError);
- }
-
- this.setState({ isCreatingJob: false });
- this.props.onClose();
- };
-
- public addErrorToast = (error: MLError) => {
- const { core } = this.context;
-
- const { urlParams } = this.props;
- const { serviceName } = urlParams;
-
- if (!serviceName) {
- return;
- }
-
- const errorDescription = error?.body?.message;
- const errorText = errorDescription
- ? `${error.message}: ${errorDescription}`
- : error.message;
-
- core.notifications.toasts.addWarning({
- title: i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreationFailedNotificationTitle',
- {
- defaultMessage: 'Job creation failed',
- }
- ),
- text: toMountPoint(
- <>
- {errorText}
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreationFailedNotificationText',
- {
- defaultMessage:
- 'Your current license may not allow for creating machine learning jobs, or this job may already exist.',
- }
- )}
-
- >
- ),
- });
- };
-
- public addSuccessToast = ({
- transactionType,
- }: {
- transactionType: string;
- }) => {
- const { core } = this.context;
- const { urlParams } = this.props;
- const { serviceName } = urlParams;
-
- if (!serviceName) {
- return;
- }
-
- core.notifications.toasts.addSuccess({
- title: i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationTitle',
- {
- defaultMessage: 'Job successfully created',
- }
- ),
- text: toMountPoint(
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText',
- {
- defaultMessage:
- 'The analysis is now running for {serviceName} ({transactionType}). It might take a while before results are added to the response times graph.',
- values: {
- serviceName,
- transactionType,
- },
- }
- )}{' '}
-
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText.viewJobLinkText',
- {
- defaultMessage: 'View job',
- }
- )}
-
-
-
- ),
- });
- };
-
- public render() {
- const { isOpen, onClose, urlParams } = this.props;
- const { serviceName } = urlParams;
- const { isCreatingJob } = this.state;
-
- if (!isOpen || !serviceName) {
- return null;
- }
-
- return (
-
- );
- }
-}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/view.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/view.tsx
deleted file mode 100644
index 72e8193ba2de2..0000000000000
--- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/MachineLearningFlyout/view.tsx
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import {
- EuiButton,
- EuiCallOut,
- EuiFlexGroup,
- EuiFlexItem,
- EuiFlyout,
- EuiFlyoutBody,
- EuiFlyoutFooter,
- EuiFlyoutHeader,
- EuiFormRow,
- EuiSpacer,
- EuiText,
- EuiTitle,
-} from '@elastic/eui';
-import { i18n } from '@kbn/i18n';
-import { FormattedMessage } from '@kbn/i18n/react';
-import React, { useState, useEffect } from 'react';
-import { isEmpty } from 'lodash';
-import { FETCH_STATUS, useFetcher } from '../../../../../hooks/useFetcher';
-import { getHasMLJob } from '../../../../../services/rest/ml';
-import { MLJobLink } from '../../../../shared/Links/MachineLearningLinks/MLJobLink';
-import { MLLink } from '../../../../shared/Links/MachineLearningLinks/MLLink';
-import { TransactionSelect } from './TransactionSelect';
-import { IUrlParams } from '../../../../../context/UrlParamsContext/types';
-import { useServiceTransactionTypes } from '../../../../../hooks/useServiceTransactionTypes';
-import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext';
-
-interface Props {
- isCreatingJob: boolean;
- onClickCreate: ({ transactionType }: { transactionType: string }) => void;
- onClose: () => void;
- urlParams: IUrlParams;
-}
-
-export function MachineLearningFlyoutView({
- isCreatingJob,
- onClickCreate,
- onClose,
- urlParams,
-}: Props) {
- const { serviceName } = urlParams;
- const transactionTypes = useServiceTransactionTypes(urlParams);
-
- const [selectedTransactionType, setSelectedTransactionType] = useState<
- string | undefined
- >(undefined);
-
- const { http } = useApmPluginContext().core;
-
- const { data: hasMLJob, status } = useFetcher(
- () => {
- if (serviceName && selectedTransactionType) {
- return getHasMLJob({
- serviceName,
- transactionType: selectedTransactionType,
- http,
- });
- }
- },
- [serviceName, selectedTransactionType, http],
- { showToastOnError: false }
- );
-
- // update selectedTransactionType when list of transaction types has loaded
- useEffect(() => {
- setSelectedTransactionType(transactionTypes[0]);
- }, [transactionTypes]);
-
- if (!serviceName || !selectedTransactionType || isEmpty(transactionTypes)) {
- return null;
- }
-
- const isLoadingMLJob = status === FETCH_STATUS.LOADING;
- const isMlAvailable = status !== FETCH_STATUS.FAILURE;
-
- return (
-
-
-
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.enableAnomalyDetectionTitle',
- {
- defaultMessage: 'Enable anomaly detection',
- }
- )}
-
-
-
-
-
- {!isMlAvailable && (
-
-
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.mlNotAvailableDescription',
- {
- defaultMessage:
- 'Unable to connect to Machine learning. Make sure it is enabled in Kibana to use anomaly detection.',
- }
- )}
-
-
-
-
- )}
- {hasMLJob && (
-
-
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription',
- {
- defaultMessage:
- 'There is currently a job running for {serviceName} ({transactionType}).',
- values: {
- serviceName,
- transactionType: selectedTransactionType,
- },
- }
- )}{' '}
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription.viewJobLinkText',
- {
- defaultMessage: 'View existing job',
- }
- )}
-
-
-
-
-
- )}
-
-
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createMLJobDescription.transactionDurationGraphText',
- {
- defaultMessage: 'transaction duration',
- }
- )}
-
- ),
- serviceMapAnnotationText: (
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createMLJobDescription.serviceMapAnnotationText',
- {
- defaultMessage: 'service maps',
- }
- )}
-
- ),
- }}
- />
-
-
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.manageMLJobDescription.mlJobsPageLinkText',
- {
- defaultMessage: 'Machine Learning Job Management page',
- }
- )}
-
- ),
- }}
- />{' '}
-
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.manageMLJobDescription.noteText',
- {
- defaultMessage:
- 'Note: It might take a few minutes for the job to begin calculating results.',
- }
- )}
-
-
-
-
-
-
-
-
-
- {transactionTypes.length > 1 ? (
- {
- setSelectedTransactionType(value);
- }}
- />
- ) : null}
-
-
-
-
- onClickCreate({ transactionType: selectedTransactionType })
- }
- fill
- disabled={isCreatingJob || hasMLJob || isLoadingMLJob}
- >
- {i18n.translate(
- 'xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createNewJobButtonLabel',
- {
- defaultMessage: 'Create job',
- }
- )}
-
-
-
-
-
-
- );
-}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx
index 321617ed8496a..0a7dcbd0be3df 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/index.tsx
@@ -4,18 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import {
- EuiButtonEmpty,
- EuiContextMenu,
- EuiContextMenuPanelItemDescriptor,
- EuiPopover,
-} from '@elastic/eui';
+import { EuiButtonEmpty, EuiContextMenu, EuiPopover } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { memoize } from 'lodash';
-import React, { Fragment } from 'react';
+import React from 'react';
import { IUrlParams } from '../../../../context/UrlParamsContext/types';
-import { LicenseContext } from '../../../../context/LicenseContext';
-import { MachineLearningFlyout } from './MachineLearningFlyout';
import { WatcherFlyout } from './WatcherFlyout';
import { ApmPluginContext } from '../../../../context/ApmPluginContext';
@@ -26,7 +18,7 @@ interface State {
isPopoverOpen: boolean;
activeFlyout: FlyoutName;
}
-type FlyoutName = null | 'ML' | 'Watcher';
+type FlyoutName = null | 'Watcher';
export class ServiceIntegrations extends React.Component {
static contextType = ApmPluginContext;
@@ -34,38 +26,6 @@ export class ServiceIntegrations extends React.Component {
public state: State = { isPopoverOpen: false, activeFlyout: null };
- public getPanelItems = memoize((mlAvailable: boolean | undefined) => {
- let panelItems: EuiContextMenuPanelItemDescriptor[] = [];
- if (mlAvailable) {
- panelItems = panelItems.concat(this.getMLPanelItems());
- }
- return panelItems.concat(this.getWatcherPanelItems());
- });
-
- public getMLPanelItems = () => {
- return [
- {
- name: i18n.translate(
- 'xpack.apm.serviceDetails.integrationsMenu.enableMLAnomalyDetectionButtonLabel',
- {
- defaultMessage: 'Enable ML anomaly detection',
- }
- ),
- icon: 'machineLearningApp',
- toolTipContent: i18n.translate(
- 'xpack.apm.serviceDetails.integrationsMenu.enableMLAnomalyDetectionButtonTooltip',
- {
- defaultMessage: 'Set up a machine learning job for this service',
- }
- ),
- onClick: () => {
- this.closePopover();
- this.openFlyout('ML');
- },
- },
- ];
- };
-
public getWatcherPanelItems = () => {
const { core } = this.context;
@@ -132,42 +92,31 @@ export class ServiceIntegrations extends React.Component {
);
return (
-
- {(license) => (
-
-
-
-
-
- )}
-
+ <>
+
+
+ >
);
}
}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
index ff68288916af4..78779bdcc2052 100644
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
+++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx
@@ -15,8 +15,6 @@ import React, { MouseEvent } from 'react';
import { Buttons } from './Buttons';
import { Info } from './Info';
import { ServiceMetricFetcher } from './ServiceMetricFetcher';
-import { AnomalyDetection } from './anomaly_detection';
-import { ServiceNode } from '../../../../../common/service_map';
import { popoverMinWidth } from '../cytoscapeOptions';
interface ContentsProps {
@@ -70,12 +68,13 @@ export function Contents({
- {isService && (
+ {/* //TODO [APM ML] add service health stats here:
+ isService && (
-
+
- )}
+ )*/}
{isService ? (
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx
deleted file mode 100644
index 531bbb139d58b..0000000000000
--- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/anomaly_detection.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { i18n } from '@kbn/i18n';
-import React from 'react';
-import styled from 'styled-components';
-import {
- EuiFlexGroup,
- EuiFlexItem,
- EuiTitle,
- EuiIconTip,
- EuiHealth,
-} from '@elastic/eui';
-import { useTheme } from '../../../../hooks/useTheme';
-import { fontSize, px } from '../../../../style/variables';
-import { asInteger } from '../../../../utils/formatters';
-import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink';
-import { getSeverityColor, popoverMinWidth } from '../cytoscapeOptions';
-import { getMetricChangeDescription } from '../../../../../../ml/public';
-import { ServiceNode } from '../../../../../common/service_map';
-
-const HealthStatusTitle = styled(EuiTitle)`
- display: inline;
- text-transform: uppercase;
-`;
-
-const VerticallyCentered = styled.div`
- display: flex;
- align-items: center;
-`;
-
-const SubduedText = styled.span`
- color: ${({ theme }) => theme.eui.euiTextSubduedColor};
-`;
-
-const EnableText = styled.section`
- color: ${({ theme }) => theme.eui.euiTextSubduedColor};
- line-height: 1.4;
- font-size: ${fontSize};
- width: ${px(popoverMinWidth)};
-`;
-
-export const ContentLine = styled.section`
- line-height: 2;
-`;
-
-interface AnomalyDetectionProps {
- serviceNodeData: cytoscape.NodeDataDefinition & ServiceNode;
-}
-
-export function AnomalyDetection({ serviceNodeData }: AnomalyDetectionProps) {
- const theme = useTheme();
- const anomalySeverity = serviceNodeData.anomaly_severity;
- const anomalyScore = serviceNodeData.anomaly_score;
- const actualValue = serviceNodeData.actual_value;
- const typicalValue = serviceNodeData.typical_value;
- const mlJobId = serviceNodeData.ml_job_id;
- const hasAnomalyDetectionScore =
- anomalySeverity !== undefined && anomalyScore !== undefined;
- const anomalyDescription =
- hasAnomalyDetectionScore &&
- actualValue !== undefined &&
- typicalValue !== undefined
- ? getMetricChangeDescription(actualValue, typicalValue).message
- : null;
-
- return (
- <>
-
-
- {ANOMALY_DETECTION_TITLE}
-
-
-
- {!mlJobId && {ANOMALY_DETECTION_DISABLED_TEXT} }
-
- {hasAnomalyDetectionScore && (
-
-
-
-
-
- {ANOMALY_DETECTION_SCORE_METRIC}
-
-
-
-
- {getDisplayedAnomalyScore(anomalyScore as number)}
- {anomalyDescription && (
- ({anomalyDescription})
- )}
-
-
-
-
- )}
- {mlJobId && !hasAnomalyDetectionScore && (
- {ANOMALY_DETECTION_NO_DATA_TEXT}
- )}
- {mlJobId && (
-
-
- {ANOMALY_DETECTION_LINK}
-
-
- )}
- >
- );
-}
-
-function getDisplayedAnomalyScore(score: number) {
- if (score > 0 && score < 1) {
- return '< 1';
- }
- return asInteger(score);
-}
-
-const ANOMALY_DETECTION_TITLE = i18n.translate(
- 'xpack.apm.serviceMap.anomalyDetectionPopoverTitle',
- { defaultMessage: 'Anomaly Detection' }
-);
-
-const ANOMALY_DETECTION_TOOLTIP = i18n.translate(
- 'xpack.apm.serviceMap.anomalyDetectionPopoverTooltip',
- {
- defaultMessage:
- 'Service health indicators are powered by the anomaly detection feature in machine learning',
- }
-);
-
-const ANOMALY_DETECTION_SCORE_METRIC = i18n.translate(
- 'xpack.apm.serviceMap.anomalyDetectionPopoverScoreMetric',
- { defaultMessage: 'Score (max.)' }
-);
-
-const ANOMALY_DETECTION_LINK = i18n.translate(
- 'xpack.apm.serviceMap.anomalyDetectionPopoverLink',
- { defaultMessage: 'View anomalies' }
-);
-
-const ANOMALY_DETECTION_DISABLED_TEXT = i18n.translate(
- 'xpack.apm.serviceMap.anomalyDetectionPopoverDisabled',
- {
- defaultMessage:
- 'Display service health indicators by enabling anomaly detection from the Integrations menu in the Service details view.',
- }
-);
-
-const ANOMALY_DETECTION_NO_DATA_TEXT = i18n.translate(
- 'xpack.apm.serviceMap.anomalyDetectionPopoverNoData',
- {
- defaultMessage: `We couldn't find an anomaly score within the selected time range. See details in the anomaly explorer.`,
- }
-);
diff --git a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx
index 9018fbb2bc410..fc5347d081316 100644
--- a/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/TransactionOverview/index.tsx
@@ -22,8 +22,6 @@ import { TransactionCharts } from '../../shared/charts/TransactionCharts';
import { TransactionBreakdown } from '../../shared/TransactionBreakdown';
import { TransactionList } from './List';
import { useRedirect } from './useRedirect';
-import { useFetcher } from '../../../hooks/useFetcher';
-import { getHasMLJob } from '../../../services/rest/ml';
import { history } from '../../../utils/history';
import { useLocation } from '../../../hooks/useLocation';
import { ChartsSyncContextProvider } from '../../../context/ChartsSyncContext';
@@ -34,7 +32,6 @@ import { PROJECTION } from '../../../../common/projections/typings';
import { useUrlParams } from '../../../hooks/useUrlParams';
import { useServiceTransactionTypes } from '../../../hooks/useServiceTransactionTypes';
import { TransactionTypeFilter } from '../../shared/LocalUIFilters/TransactionTypeFilter';
-import { useApmPluginContext } from '../../../hooks/useApmPluginContext';
function getRedirectLocation({
urlParams,
@@ -86,18 +83,6 @@ export function TransactionOverview() {
status: transactionListStatus,
} = useTransactionList(urlParams);
- const { http } = useApmPluginContext().core;
-
- const { data: hasMLJob = false } = useFetcher(
- () => {
- if (serviceName && transactionType) {
- return getHasMLJob({ serviceName, transactionType, http });
- }
- },
- [http, serviceName, transactionType],
- { showToastOnError: false }
- );
-
const localFiltersConfig: React.ComponentProps = useMemo(
() => ({
filterNames: [
@@ -140,7 +125,8 @@ export function TransactionOverview() {
{
- it('should produce the correct URL with serviceName', async () => {
- const href = await getRenderedHref(
- () => (
-
- ),
- { search: '?rangeFrom=now/w&rangeTo=now-4h' } as Location
- );
-
- expect(href).toEqual(
- `/basepath/app/ml#/timeseriesexplorer?_g=(ml:(jobIds:!(myservicename-mytransactiontype-high_mean_response_time)),refreshInterval:(pause:true,value:'0'),time:(from:now%2Fw,to:now-4h))`
- );
- });
it('should produce the correct URL with jobId', async () => {
const href = await getRenderedHref(
() => (
diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.tsx
index 346748964d529..1e1f9ea5f23b7 100644
--- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.tsx
+++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLJobLink.tsx
@@ -5,28 +5,16 @@
*/
import React from 'react';
-import { getMlJobId } from '../../../../../common/ml_job_constants';
import { MLLink } from './MLLink';
-interface PropsServiceName {
- serviceName: string;
- transactionType?: string;
-}
-interface PropsJobId {
+interface Props {
jobId: string;
-}
-
-type Props = (PropsServiceName | PropsJobId) & {
external?: boolean;
-};
+}
export const MLJobLink: React.FC = (props) => {
- const jobId =
- 'jobId' in props
- ? props.jobId
- : getMlJobId(props.serviceName, props.transactionType);
const query = {
- ml: { jobIds: [jobId] },
+ ml: { jobIds: [props.jobId] },
};
return (
diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
index 4821e06419e34..00ff6f9969725 100644
--- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.tsx
@@ -101,11 +101,13 @@ export class TransactionCharts extends Component {
return null;
}
- const { serviceName, transactionType, kuery } = this.props.urlParams;
+ const { serviceName, kuery } = this.props.urlParams;
if (!serviceName) {
return null;
}
+ const linkedJobId = ''; // TODO [APM ML] link to ML job id for the selected environment
+
const hasKuery = !isEmpty(kuery);
const icon = hasKuery ? (
{
}
)}{' '}
-
- View Job
-
+ View Job
);
diff --git a/x-pack/plugins/apm/public/services/rest/ml.ts b/x-pack/plugins/apm/public/services/rest/ml.ts
deleted file mode 100644
index 47032501d9fbe..0000000000000
--- a/x-pack/plugins/apm/public/services/rest/ml.ts
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { HttpSetup } from 'kibana/public';
-import {
- PROCESSOR_EVENT,
- SERVICE_NAME,
- TRANSACTION_TYPE,
-} from '../../../common/elasticsearch_fieldnames';
-import {
- APM_ML_JOB_GROUP_NAME,
- getMlJobId,
- getMlPrefix,
- encodeForMlApi,
-} from '../../../common/ml_job_constants';
-import { callApi } from './callApi';
-import { ESFilter } from '../../../typings/elasticsearch';
-import { callApmApi } from './createCallApmApi';
-
-interface MlResponseItem {
- id: string;
- success: boolean;
- error?: {
- msg: string;
- body: string;
- path: string;
- response: string;
- statusCode: number;
- };
-}
-
-interface StartedMLJobApiResponse {
- datafeeds: MlResponseItem[];
- jobs: MlResponseItem[];
-}
-
-async function getTransactionIndices() {
- const indices = await callApmApi({
- method: 'GET',
- pathname: `/api/apm/settings/apm-indices`,
- });
- return indices['apm_oss.transactionIndices'];
-}
-
-export async function startMLJob({
- serviceName,
- transactionType,
- http,
-}: {
- serviceName: string;
- transactionType: string;
- http: HttpSetup;
-}) {
- const transactionIndices = await getTransactionIndices();
- const groups = [
- APM_ML_JOB_GROUP_NAME,
- encodeForMlApi(serviceName),
- encodeForMlApi(transactionType),
- ];
- const filter: ESFilter[] = [
- { term: { [SERVICE_NAME]: serviceName } },
- { term: { [PROCESSOR_EVENT]: 'transaction' } },
- { term: { [TRANSACTION_TYPE]: transactionType } },
- ];
- return callApi(http, {
- method: 'POST',
- pathname: `/api/ml/modules/setup/apm_transaction`,
- body: {
- prefix: getMlPrefix(serviceName, transactionType),
- groups,
- indexPatternName: transactionIndices,
- startDatafeed: true,
- query: {
- bool: {
- filter,
- },
- },
- },
- });
-}
-
-// https://www.elastic.co/guide/en/elasticsearch/reference/6.5/ml-get-job.html
-export interface MLJobApiResponse {
- count: number;
- jobs: Array<{
- job_id: string;
- }>;
-}
-
-export type MLError = Error & { body?: { message?: string } };
-
-export async function getHasMLJob({
- serviceName,
- transactionType,
- http,
-}: {
- serviceName: string;
- transactionType: string;
- http: HttpSetup;
-}) {
- try {
- await callApi(http, {
- method: 'GET',
- pathname: `/api/ml/anomaly_detectors/${getMlJobId(
- serviceName,
- transactionType
- )}`,
- });
- return true;
- } catch (error) {
- if (
- error?.body?.statusCode === 404 &&
- error?.body?.attributes?.body?.error?.type ===
- 'resource_not_found_exception'
- ) {
- return false; // false only if ML api responds with resource_not_found_exception
- }
- throw error;
- }
-}
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.test.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.test.ts
deleted file mode 100644
index aefd074c373f9..0000000000000
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.test.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { getApmMlJobCategory } from './get_service_anomalies';
-import { Job as AnomalyDetectionJob } from '../../../../ml/server';
-
-describe('getApmMlJobCategory', () => {
- it('should match service names with different casings', () => {
- const mlJob = {
- job_id: 'testservice-request-high_mean_response_time',
- groups: ['apm', 'testservice', 'request'],
- } as AnomalyDetectionJob;
- const serviceNames = ['testService'];
- const apmMlJobCategory = getApmMlJobCategory(mlJob, serviceNames);
-
- expect(apmMlJobCategory).toEqual({
- jobId: 'testservice-request-high_mean_response_time',
- serviceName: 'testService',
- transactionType: 'request',
- });
- });
-
- it('should match service names with spaces', () => {
- const mlJob = {
- job_id: 'test_service-request-high_mean_response_time',
- groups: ['apm', 'test_service', 'request'],
- } as AnomalyDetectionJob;
- const serviceNames = ['Test Service'];
- const apmMlJobCategory = getApmMlJobCategory(mlJob, serviceNames);
-
- expect(apmMlJobCategory).toEqual({
- jobId: 'test_service-request-high_mean_response_time',
- serviceName: 'Test Service',
- transactionType: 'request',
- });
- });
-});
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts
deleted file mode 100644
index 900141e9040ae..0000000000000
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_anomalies.ts
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-import { intersection } from 'lodash';
-import { leftJoin } from '../../../common/utils/left_join';
-import { Job as AnomalyDetectionJob } from '../../../../ml/server';
-import { PromiseReturnType } from '../../../typings/common';
-import { IEnvOptions } from './get_service_map';
-import { Setup } from '../helpers/setup_request';
-import {
- APM_ML_JOB_GROUP_NAME,
- encodeForMlApi,
-} from '../../../common/ml_job_constants';
-
-async function getApmAnomalyDetectionJobs(
- setup: Setup
-): Promise {
- const { ml } = setup;
-
- if (!ml) {
- return [];
- }
- try {
- const { jobs } = await ml.anomalyDetectors.jobs(APM_ML_JOB_GROUP_NAME);
- return jobs;
- } catch (error) {
- if (error.statusCode === 404) {
- return [];
- }
- throw error;
- }
-}
-
-type ApmMlJobCategory = NonNullable>;
-
-export const getApmMlJobCategory = (
- mlJob: AnomalyDetectionJob,
- serviceNames: string[]
-) => {
- const serviceByGroupNameMap = new Map(
- serviceNames.map((serviceName) => [
- encodeForMlApi(serviceName),
- serviceName,
- ])
- );
- if (!mlJob.groups.includes(APM_ML_JOB_GROUP_NAME)) {
- // ML job missing "apm" group name
- return;
- }
- const apmJobGroups = mlJob.groups.filter(
- (groupName) => groupName !== APM_ML_JOB_GROUP_NAME
- );
- const apmJobServiceNames = apmJobGroups.map(
- (groupName) => serviceByGroupNameMap.get(groupName) || groupName
- );
- const [serviceName] = intersection(apmJobServiceNames, serviceNames);
- if (!serviceName) {
- // APM ML job service was not found
- return;
- }
- const serviceGroupName = encodeForMlApi(serviceName);
- const [transactionType] = apmJobGroups.filter(
- (groupName) => groupName !== serviceGroupName
- );
- if (!transactionType) {
- // APM ML job transaction type was not found.
- return;
- }
- return { jobId: mlJob.job_id, serviceName, transactionType };
-};
-
-export type ServiceAnomalies = PromiseReturnType;
-
-export async function getServiceAnomalies(
- options: IEnvOptions,
- serviceNames: string[]
-) {
- const { start, end, ml } = options.setup;
-
- if (!ml || serviceNames.length === 0) {
- return [];
- }
-
- const apmMlJobs = await getApmAnomalyDetectionJobs(options.setup);
- if (apmMlJobs.length === 0) {
- return [];
- }
- const apmMlJobCategories = apmMlJobs
- .map((job) => getApmMlJobCategory(job, serviceNames))
- .filter(
- (apmJobCategory) => apmJobCategory !== undefined
- ) as ApmMlJobCategory[];
- const apmJobIds = apmMlJobs.map((job) => job.job_id);
- const params = {
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { result_type: 'record' } },
- {
- terms: {
- job_id: apmJobIds,
- },
- },
- {
- range: {
- timestamp: { gte: start, lte: end, format: 'epoch_millis' },
- },
- },
- ],
- },
- },
- aggs: {
- jobs: {
- terms: { field: 'job_id', size: apmJobIds.length },
- aggs: {
- top_score_hits: {
- top_hits: {
- sort: [{ record_score: { order: 'desc' as const } }],
- _source: ['record_score', 'timestamp', 'typical', 'actual'],
- size: 1,
- },
- },
- },
- },
- },
- },
- };
-
- const response = (await ml.mlSystem.mlAnomalySearch(params)) as {
- aggregations: {
- jobs: {
- buckets: Array<{
- key: string;
- top_score_hits: {
- hits: {
- hits: Array<{
- _source: {
- record_score: number;
- timestamp: number;
- typical: number[];
- actual: number[];
- };
- }>;
- };
- };
- }>;
- };
- };
- };
- const anomalyScores = response.aggregations.jobs.buckets.map((jobBucket) => {
- const jobId = jobBucket.key;
- const bucketSource = jobBucket.top_score_hits.hits.hits?.[0]?._source;
- return {
- jobId,
- anomalyScore: bucketSource.record_score,
- timestamp: bucketSource.timestamp,
- typical: bucketSource.typical[0],
- actual: bucketSource.actual[0],
- };
- });
- return leftJoin(apmMlJobCategories, 'jobId', anomalyScores);
-}
diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
index 9f3ded82d7cbd..4d488cd1a5509 100644
--- a/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map.ts
@@ -13,14 +13,9 @@ import { getServicesProjection } from '../../../common/projections/services';
import { mergeProjection } from '../../../common/projections/util/merge_projection';
import { PromiseReturnType } from '../../../typings/common';
import { Setup, SetupTimeRange } from '../helpers/setup_request';
-import {
- transformServiceMapResponses,
- getAllNodes,
- getServiceNodes,
-} from './transform_service_map_responses';
+import { transformServiceMapResponses } from './transform_service_map_responses';
import { getServiceMapFromTraceIds } from './get_service_map_from_trace_ids';
import { getTraceSampleIds } from './get_trace_sample_ids';
-import { getServiceAnomalies, ServiceAnomalies } from './get_service_anomalies';
export interface IEnvOptions {
setup: Setup & SetupTimeRange;
@@ -132,7 +127,6 @@ async function getServicesData(options: IEnvOptions) {
);
}
-export { ServiceAnomalies };
export type ConnectionsResponse = PromiseReturnType;
export type ServicesResponse = PromiseReturnType;
export type ServiceMapAPIResponse = PromiseReturnType;
@@ -143,19 +137,8 @@ export async function getServiceMap(options: IEnvOptions) {
getServicesData(options),
]);
- // Derive all related service names from connection and service data
- const allNodes = getAllNodes(servicesData, connectionData.connections);
- const serviceNodes = getServiceNodes(allNodes);
- const serviceNames = serviceNodes.map(
- (serviceData) => serviceData[SERVICE_NAME]
- );
-
- // Get related service anomalies
- const serviceAnomalies = await getServiceAnomalies(options, serviceNames);
-
return transformServiceMapResponses({
...connectionData,
- anomalies: serviceAnomalies,
services: servicesData,
});
}
diff --git a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts
deleted file mode 100644
index f07b575cc0a35..0000000000000
--- a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.test.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { ServiceAnomalies } from './get_service_map';
-import { addAnomaliesDataToNodes } from './ml_helpers';
-
-describe('addAnomaliesDataToNodes', () => {
- it('adds anomalies to nodes', () => {
- const nodes = [
- {
- 'service.name': 'opbeans-ruby',
- 'agent.name': 'ruby',
- 'service.environment': null,
- },
- {
- 'service.name': 'opbeans-java',
- 'agent.name': 'java',
- 'service.environment': null,
- },
- ];
-
- const serviceAnomalies: ServiceAnomalies = [
- {
- jobId: 'opbeans-ruby-request-high_mean_response_time',
- serviceName: 'opbeans-ruby',
- transactionType: 'request',
- anomalyScore: 50,
- timestamp: 1591351200000,
- actual: 2000,
- typical: 1000,
- },
- {
- jobId: 'opbeans-java-request-high_mean_response_time',
- serviceName: 'opbeans-java',
- transactionType: 'request',
- anomalyScore: 100,
- timestamp: 1591351200000,
- actual: 9000,
- typical: 3000,
- },
- ];
-
- const result = [
- {
- 'service.name': 'opbeans-ruby',
- 'agent.name': 'ruby',
- 'service.environment': null,
- anomaly_score: 50,
- anomaly_severity: 'major',
- actual_value: 2000,
- typical_value: 1000,
- ml_job_id: 'opbeans-ruby-request-high_mean_response_time',
- },
- {
- 'service.name': 'opbeans-java',
- 'agent.name': 'java',
- 'service.environment': null,
- anomaly_score: 100,
- anomaly_severity: 'critical',
- actual_value: 9000,
- typical_value: 3000,
- ml_job_id: 'opbeans-java-request-high_mean_response_time',
- },
- ];
-
- expect(
- addAnomaliesDataToNodes(
- nodes,
- (serviceAnomalies as unknown) as ServiceAnomalies
- )
- ).toEqual(result);
- });
-});
diff --git a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts b/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts
deleted file mode 100644
index 8162417616b6c..0000000000000
--- a/x-pack/plugins/apm/server/lib/service_map/ml_helpers.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { SERVICE_NAME } from '../../../common/elasticsearch_fieldnames';
-import { getSeverity } from '../../../common/ml_job_constants';
-import { ConnectionNode, ServiceNode } from '../../../common/service_map';
-import { ServiceAnomalies } from './get_service_map';
-
-export function addAnomaliesDataToNodes(
- nodes: ConnectionNode[],
- serviceAnomalies: ServiceAnomalies
-) {
- const anomaliesMap = serviceAnomalies.reduce(
- (acc, anomalyJob) => {
- const serviceAnomaly: typeof acc[string] | undefined =
- acc[anomalyJob.serviceName];
- const hasAnomalyJob = serviceAnomaly !== undefined;
- const hasAnomalyScore = serviceAnomaly?.anomaly_score !== undefined;
- const hasNewAnomalyScore = anomalyJob.anomalyScore !== undefined;
- const hasNewMaxAnomalyScore =
- hasNewAnomalyScore &&
- (!hasAnomalyScore ||
- (anomalyJob?.anomalyScore ?? 0) >
- (serviceAnomaly?.anomaly_score ?? 0));
-
- if (!hasAnomalyJob || hasNewMaxAnomalyScore) {
- acc[anomalyJob.serviceName] = {
- anomaly_score: anomalyJob.anomalyScore,
- actual_value: anomalyJob.actual,
- typical_value: anomalyJob.typical,
- ml_job_id: anomalyJob.jobId,
- };
- }
-
- return acc;
- },
- {} as {
- [serviceName: string]: {
- anomaly_score?: number;
- actual_value?: number;
- typical_value?: number;
- ml_job_id: string;
- };
- }
- );
-
- const servicesDataWithAnomalies: ServiceNode[] = nodes.map((service) => {
- const serviceAnomaly = anomaliesMap[service[SERVICE_NAME]];
- if (serviceAnomaly) {
- const anomalyScore = serviceAnomaly.anomaly_score;
- return {
- ...service,
- anomaly_score: anomalyScore,
- anomaly_severity: getSeverity(anomalyScore),
- actual_value: serviceAnomaly.actual_value,
- typical_value: serviceAnomaly.typical_value,
- ml_job_id: serviceAnomaly.ml_job_id,
- };
- }
- return service;
- });
-
- return servicesDataWithAnomalies;
-}
diff --git a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts
index 6c9880c2dc4df..1e26634bdf0f1 100644
--- a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.test.ts
@@ -12,7 +12,6 @@ import {
SPAN_SUBTYPE,
SPAN_TYPE,
} from '../../../common/elasticsearch_fieldnames';
-import { ServiceAnomalies } from './get_service_map';
import {
transformServiceMapResponses,
ServiceMapResponse,
@@ -36,12 +35,9 @@ const javaService = {
[AGENT_NAME]: 'java',
};
-const serviceAnomalies: ServiceAnomalies = [];
-
describe('transformServiceMapResponses', () => {
it('maps external destinations to internal services', () => {
const response: ServiceMapResponse = {
- anomalies: serviceAnomalies,
services: [nodejsService, javaService],
discoveredServices: [
{
@@ -73,7 +69,6 @@ describe('transformServiceMapResponses', () => {
it('collapses external destinations based on span.destination.resource.name', () => {
const response: ServiceMapResponse = {
- anomalies: serviceAnomalies,
services: [nodejsService, javaService],
discoveredServices: [
{
@@ -109,7 +104,6 @@ describe('transformServiceMapResponses', () => {
it('picks the first span.type/subtype in an alphabetically sorted list', () => {
const response: ServiceMapResponse = {
- anomalies: serviceAnomalies,
services: [javaService],
discoveredServices: [],
connections: [
@@ -148,7 +142,6 @@ describe('transformServiceMapResponses', () => {
it('processes connections without a matching "service" aggregation', () => {
const response: ServiceMapResponse = {
- anomalies: serviceAnomalies,
services: [javaService],
discoveredServices: [],
connections: [
diff --git a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts
index 53abf54cbcf31..835c00b8df239 100644
--- a/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts
+++ b/x-pack/plugins/apm/server/lib/service_map/transform_service_map_responses.ts
@@ -17,12 +17,7 @@ import {
ServiceConnectionNode,
ExternalConnectionNode,
} from '../../../common/service_map';
-import {
- ConnectionsResponse,
- ServicesResponse,
- ServiceAnomalies,
-} from './get_service_map';
-import { addAnomaliesDataToNodes } from './ml_helpers';
+import { ConnectionsResponse, ServicesResponse } from './get_service_map';
function getConnectionNodeId(node: ConnectionNode): string {
if ('span.destination.service.resource' in node) {
@@ -67,12 +62,11 @@ export function getServiceNodes(allNodes: ConnectionNode[]) {
}
export type ServiceMapResponse = ConnectionsResponse & {
- anomalies: ServiceAnomalies;
services: ServicesResponse;
};
export function transformServiceMapResponses(response: ServiceMapResponse) {
- const { anomalies, discoveredServices, services, connections } = response;
+ const { discoveredServices, services, connections } = response;
const allNodes = getAllNodes(services, connections);
const serviceNodes = getServiceNodes(allNodes);
@@ -214,18 +208,10 @@ export function transformServiceMapResponses(response: ServiceMapResponse) {
return prev.concat(connection);
}, []);
- // Add anomlies data
- const dedupedNodesWithAnomliesData = addAnomaliesDataToNodes(
- dedupedNodes,
- anomalies
- );
-
// Put everything together in elements, with everything in the "data" property
- const elements = [...dedupedConnections, ...dedupedNodesWithAnomliesData].map(
- (element) => ({
- data: element,
- })
- );
+ const elements = [...dedupedConnections, ...dedupedNodes].map((element) => ({
+ data: element,
+ }));
return { elements };
}
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/fetcher.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/fetcher.test.ts.snap
deleted file mode 100644
index cf3fdac221b59..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/fetcher.test.ts.snap
+++ /dev/null
@@ -1,68 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`anomalyAggsFetcher when ES returns valid response should call client with correct query 1`] = `
-Array [
- Array [
- Object {
- "body": Object {
- "aggs": Object {
- "ml_avg_response_times": Object {
- "aggs": Object {
- "anomaly_score": Object {
- "max": Object {
- "field": "anomaly_score",
- },
- },
- "lower": Object {
- "min": Object {
- "field": "model_lower",
- },
- },
- "upper": Object {
- "max": Object {
- "field": "model_upper",
- },
- },
- },
- "date_histogram": Object {
- "extended_bounds": Object {
- "max": 200000,
- "min": 90000,
- },
- "field": "timestamp",
- "fixed_interval": "myInterval",
- "min_doc_count": 0,
- },
- },
- },
- "query": Object {
- "bool": Object {
- "filter": Array [
- Object {
- "term": Object {
- "job_id": "myservicename-mytransactiontype-high_mean_response_time",
- },
- },
- Object {
- "exists": Object {
- "field": "bucket_span",
- },
- },
- Object {
- "range": Object {
- "timestamp": Object {
- "format": "epoch_millis",
- "gte": 90000,
- "lte": 200000,
- },
- },
- },
- ],
- },
- },
- "size": 0,
- },
- },
- ],
-]
-`;
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/index.test.ts.snap
deleted file mode 100644
index 971fa3b92cc83..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/index.test.ts.snap
+++ /dev/null
@@ -1,38 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`getAnomalySeries should match snapshot 1`] = `
-Object {
- "anomalyBoundaries": Array [
- Object {
- "x": 5000,
- "y": 200,
- "y0": 20,
- },
- Object {
- "x": 15000,
- "y": 100,
- "y0": 20,
- },
- Object {
- "x": 25000,
- "y": 50,
- "y0": 10,
- },
- Object {
- "x": 30000,
- "y": 50,
- "y0": 10,
- },
- ],
- "anomalyScore": Array [
- Object {
- "x": 25000,
- "x0": 15000,
- },
- Object {
- "x": 35000,
- "x0": 25000,
- },
- ],
-}
-`;
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/transform.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/transform.test.ts.snap
deleted file mode 100644
index 8cf471cb34ed2..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/__snapshots__/transform.test.ts.snap
+++ /dev/null
@@ -1,33 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`anomalySeriesTransform should match snapshot 1`] = `
-Object {
- "anomalyBoundaries": Array [
- Object {
- "x": 10000,
- "y": 200,
- "y0": 20,
- },
- Object {
- "x": 15000,
- "y": 100,
- "y0": 20,
- },
- Object {
- "x": 25000,
- "y": 50,
- "y0": 10,
- },
- ],
- "anomalyScore": Array [
- Object {
- "x": 25000,
- "x0": 15000,
- },
- Object {
- "x": 25000,
- "x0": 25000,
- },
- ],
-}
-`;
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.test.ts
deleted file mode 100644
index 313cf818a322d..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.test.ts
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { anomalySeriesFetcher, ESResponse } from './fetcher';
-
-describe('anomalyAggsFetcher', () => {
- describe('when ES returns valid response', () => {
- let response: ESResponse | undefined;
- let clientSpy: jest.Mock;
-
- beforeEach(async () => {
- clientSpy = jest.fn().mockReturnValue('ES Response');
- response = await anomalySeriesFetcher({
- serviceName: 'myServiceName',
- transactionType: 'myTransactionType',
- intervalString: 'myInterval',
- mlBucketSize: 10,
- setup: {
- ml: {
- mlSystem: {
- mlAnomalySearch: clientSpy,
- },
- } as any,
- start: 100000,
- end: 200000,
- } as any,
- });
- });
-
- it('should call client with correct query', () => {
- expect(clientSpy.mock.calls).toMatchSnapshot();
- });
-
- it('should return correct response', () => {
- expect(response).toBe('ES Response');
- });
- });
-
- it('should swallow HTTP errors', () => {
- const httpError = new Error('anomaly lookup failed') as any;
- httpError.statusCode = 418;
- const failedRequestSpy = jest.fn(() => Promise.reject(httpError));
-
- return expect(
- anomalySeriesFetcher({
- setup: {
- ml: {
- mlSystem: {
- mlAnomalySearch: failedRequestSpy,
- },
- } as any,
- },
- } as any)
- ).resolves.toEqual(undefined);
- });
-
- it('should throw other errors', () => {
- const otherError = new Error('anomaly lookup ASPLODED') as any;
- const failedRequestSpy = jest.fn(() => Promise.reject(otherError));
-
- return expect(
- anomalySeriesFetcher({
- setup: {
- ml: {
- mlSystem: {
- mlAnomalySearch: failedRequestSpy,
- },
- } as any,
- },
- } as any)
- ).rejects.toThrow(otherError);
- });
-});
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts
deleted file mode 100644
index 8ee078de7f3ce..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/fetcher.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { getMlJobId } from '../../../../../common/ml_job_constants';
-import { PromiseReturnType } from '../../../../../../observability/typings/common';
-import { Setup, SetupTimeRange } from '../../../helpers/setup_request';
-
-export type ESResponse = Exclude<
- PromiseReturnType,
- undefined
->;
-
-export async function anomalySeriesFetcher({
- serviceName,
- transactionType,
- intervalString,
- mlBucketSize,
- setup,
-}: {
- serviceName: string;
- transactionType: string;
- intervalString: string;
- mlBucketSize: number;
- setup: Setup & SetupTimeRange;
-}) {
- const { ml, start, end } = setup;
- if (!ml) {
- return;
- }
-
- // move the start back with one bucket size, to ensure to get anomaly data in the beginning
- // this is required because ML has a minimum bucket size (default is 900s) so if our buckets are smaller, we might have several null buckets in the beginning
- const newStart = start - mlBucketSize * 1000;
- const jobId = getMlJobId(serviceName, transactionType);
-
- const params = {
- body: {
- size: 0,
- query: {
- bool: {
- filter: [
- { term: { job_id: jobId } },
- { exists: { field: 'bucket_span' } },
- {
- range: {
- timestamp: {
- gte: newStart,
- lte: end,
- format: 'epoch_millis',
- },
- },
- },
- ],
- },
- },
- aggs: {
- ml_avg_response_times: {
- date_histogram: {
- field: 'timestamp',
- fixed_interval: intervalString,
- min_doc_count: 0,
- extended_bounds: {
- min: newStart,
- max: end,
- },
- },
- aggs: {
- anomaly_score: { max: { field: 'anomaly_score' } },
- lower: { min: { field: 'model_lower' } },
- upper: { max: { field: 'model_upper' } },
- },
- },
- },
- },
- };
-
- try {
- const response = await ml.mlSystem.mlAnomalySearch(params);
- return response;
- } catch (err) {
- const isHttpError = 'statusCode' in err;
- if (isHttpError) {
- return;
- }
- throw err;
- }
-}
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts
deleted file mode 100644
index d649bfb192739..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/get_ml_bucket_size.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { getMlJobId } from '../../../../../common/ml_job_constants';
-import { Setup, SetupTimeRange } from '../../../helpers/setup_request';
-
-interface IOptions {
- serviceName: string;
- transactionType: string;
- setup: Setup & SetupTimeRange;
-}
-
-interface ESResponse {
- bucket_span: number;
-}
-
-export async function getMlBucketSize({
- serviceName,
- transactionType,
- setup,
-}: IOptions): Promise {
- const { ml, start, end } = setup;
- if (!ml) {
- return 0;
- }
- const jobId = getMlJobId(serviceName, transactionType);
-
- const params = {
- body: {
- _source: 'bucket_span',
- size: 1,
- query: {
- bool: {
- filter: [
- { term: { job_id: jobId } },
- { exists: { field: 'bucket_span' } },
- {
- range: {
- timestamp: {
- gte: start,
- lte: end,
- format: 'epoch_millis',
- },
- },
- },
- ],
- },
- },
- },
- };
-
- try {
- const resp = await ml.mlSystem.mlAnomalySearch(params);
- return resp.hits.hits[0]?._source.bucket_span || 0;
- } catch (err) {
- const isHttpError = 'statusCode' in err;
- if (isHttpError) {
- return 0;
- }
- throw err;
- }
-}
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts
deleted file mode 100644
index fb87f1b5707d1..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.test.ts
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { getAnomalySeries } from '.';
-import { mlAnomalyResponse } from './mock_responses/ml_anomaly_response';
-import { mlBucketSpanResponse } from './mock_responses/ml_bucket_span_response';
-import { PromiseReturnType } from '../../../../../../observability/typings/common';
-import { APMConfig } from '../../../..';
-
-describe('getAnomalySeries', () => {
- let avgAnomalies: PromiseReturnType;
- beforeEach(async () => {
- const clientSpy = jest
- .fn()
- .mockResolvedValueOnce(mlBucketSpanResponse)
- .mockResolvedValueOnce(mlAnomalyResponse);
-
- avgAnomalies = await getAnomalySeries({
- serviceName: 'myServiceName',
- transactionType: 'myTransactionType',
- transactionName: undefined,
- timeSeriesDates: [100, 100000],
- setup: {
- start: 0,
- end: 500000,
- client: { search: () => {} } as any,
- internalClient: { search: () => {} } as any,
- config: new Proxy(
- {},
- {
- get: () => 'myIndex',
- }
- ) as APMConfig,
- uiFiltersES: [],
- indices: {
- 'apm_oss.sourcemapIndices': 'myIndex',
- 'apm_oss.errorIndices': 'myIndex',
- 'apm_oss.onboardingIndices': 'myIndex',
- 'apm_oss.spanIndices': 'myIndex',
- 'apm_oss.transactionIndices': 'myIndex',
- 'apm_oss.metricsIndices': 'myIndex',
- apmAgentConfigurationIndex: 'myIndex',
- apmCustomLinkIndex: 'myIndex',
- },
- dynamicIndexPattern: null as any,
- ml: {
- mlSystem: {
- mlAnomalySearch: clientSpy,
- mlCapabilities: async () => ({ isPlatinumOrTrialLicense: true }),
- },
- } as any,
- },
- });
- });
-
- it('should remove buckets lower than threshold and outside date range from anomalyScore', () => {
- expect(avgAnomalies!.anomalyScore).toEqual([
- { x0: 15000, x: 25000 },
- { x0: 25000, x: 35000 },
- ]);
- });
-
- it('should remove buckets outside date range from anomalyBoundaries', () => {
- expect(
- avgAnomalies!.anomalyBoundaries!.filter(
- (bucket) => bucket.x < 100 || bucket.x > 100000
- ).length
- ).toBe(0);
- });
-
- it('should remove buckets with null from anomalyBoundaries', () => {
- expect(
- avgAnomalies!.anomalyBoundaries!.filter((p) => p.y === null).length
- ).toBe(0);
- });
-
- it('should match snapshot', async () => {
- expect(avgAnomalies).toMatchSnapshot();
- });
-});
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts
index 6f44cfa1df9f0..b2d11f2ffe19a 100644
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts
+++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/index.ts
@@ -4,15 +4,17 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { getBucketSize } from '../../../helpers/get_bucket_size';
import {
Setup,
SetupTimeRange,
SetupUIFilters,
} from '../../../helpers/setup_request';
-import { anomalySeriesFetcher } from './fetcher';
-import { getMlBucketSize } from './get_ml_bucket_size';
-import { anomalySeriesTransform } from './transform';
+import { Coordinate, RectCoordinate } from '../../../../../typings/timeseries';
+
+interface AnomalyTimeseries {
+ anomalyBoundaries: Coordinate[];
+ anomalyScore: RectCoordinate[];
+}
export async function getAnomalySeries({
serviceName,
@@ -26,7 +28,7 @@ export async function getAnomalySeries({
transactionName: string | undefined;
timeSeriesDates: number[];
setup: Setup & SetupTimeRange & SetupUIFilters;
-}) {
+}): Promise {
// don't fetch anomalies for transaction details page
if (transactionName) {
return;
@@ -53,29 +55,6 @@ export async function getAnomalySeries({
return;
}
- const mlBucketSize = await getMlBucketSize({
- serviceName,
- transactionType,
- setup,
- });
-
- const { start, end } = setup;
- const { intervalString, bucketSize } = getBucketSize(start, end, 'auto');
-
- const esResponse = await anomalySeriesFetcher({
- serviceName,
- transactionType,
- intervalString,
- mlBucketSize,
- setup,
- });
-
- return esResponse
- ? anomalySeriesTransform(
- esResponse,
- mlBucketSize,
- bucketSize,
- timeSeriesDates
- )
- : undefined;
+ // TODO [APM ML] return a series of anomaly scores, upper & lower bounds for the given timeSeriesDates
+ return;
}
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_anomaly_response.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_anomaly_response.ts
deleted file mode 100644
index 523161ec10275..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_anomaly_response.ts
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { ESResponse } from '../fetcher';
-
-export const mlAnomalyResponse: ESResponse = ({
- took: 3,
- timed_out: false,
- _shards: {
- total: 5,
- successful: 5,
- skipped: 0,
- failed: 0,
- },
- hits: {
- total: 10,
- max_score: 0,
- hits: [],
- },
- aggregations: {
- ml_avg_response_times: {
- buckets: [
- {
- key_as_string: '2018-07-02T09:16:40.000Z',
- key: 0,
- doc_count: 0,
- anomaly_score: {
- value: null,
- },
- upper: {
- value: 200,
- },
- lower: {
- value: 20,
- },
- },
- {
- key_as_string: '2018-07-02T09:25:00.000Z',
- key: 5000,
- doc_count: 4,
- anomaly_score: {
- value: null,
- },
- upper: {
- value: null,
- },
- lower: {
- value: null,
- },
- },
- {
- key_as_string: '2018-07-02T09:33:20.000Z',
- key: 10000,
- doc_count: 0,
- anomaly_score: {
- value: null,
- },
- upper: {
- value: null,
- },
- lower: {
- value: null,
- },
- },
- {
- key_as_string: '2018-07-02T09:41:40.000Z',
- key: 15000,
- doc_count: 2,
- anomaly_score: {
- value: 90,
- },
- upper: {
- value: 100,
- },
- lower: {
- value: 20,
- },
- },
- {
- key_as_string: '2018-07-02T09:50:00.000Z',
- key: 20000,
- doc_count: 0,
- anomaly_score: {
- value: null,
- },
- upper: {
- value: null,
- },
- lower: {
- value: null,
- },
- },
- {
- key_as_string: '2018-07-02T09:58:20.000Z',
- key: 25000,
- doc_count: 2,
- anomaly_score: {
- value: 100,
- },
- upper: {
- value: 50,
- },
- lower: {
- value: 10,
- },
- },
- {
- key_as_string: '2018-07-02T10:15:00.000Z',
- key: 30000,
- doc_count: 2,
- anomaly_score: {
- value: 0,
- },
- upper: {
- value: null,
- },
- lower: {
- value: null,
- },
- },
- ],
- },
- },
-} as unknown) as ESResponse;
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_bucket_span_response.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_bucket_span_response.ts
deleted file mode 100644
index 148d4a7ffcf6f..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/mock_responses/ml_bucket_span_response.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export const mlBucketSpanResponse = {
- took: 1,
- timed_out: false,
- _shards: {
- total: 1,
- successful: 1,
- skipped: 0,
- failed: 0,
- },
- hits: {
- total: 192,
- max_score: 1.0,
- hits: [
- {
- _index: '.ml-anomalies-shared',
- _type: 'doc',
- _id:
- 'opbeans-go-request-high_mean_response_time_model_plot_1542636000000_900_0_29791_0',
- _score: 1.0,
- _source: {
- bucket_span: 10,
- },
- },
- ],
- },
-};
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts
deleted file mode 100644
index eb94c83e92576..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.test.ts
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { ESResponse } from './fetcher';
-import { mlAnomalyResponse } from './mock_responses/ml_anomaly_response';
-import { anomalySeriesTransform, replaceFirstAndLastBucket } from './transform';
-
-describe('anomalySeriesTransform', () => {
- it('should match snapshot', () => {
- const getMlBucketSize = 10;
- const bucketSize = 5;
- const timeSeriesDates = [10000, 25000];
- const anomalySeries = anomalySeriesTransform(
- mlAnomalyResponse,
- getMlBucketSize,
- bucketSize,
- timeSeriesDates
- );
- expect(anomalySeries).toMatchSnapshot();
- });
-
- describe('anomalyScoreSeries', () => {
- it('should only returns bucket within range and above threshold', () => {
- const esResponse = getESResponse([
- {
- key: 0,
- anomaly_score: { value: 90 },
- },
- {
- key: 5000,
- anomaly_score: { value: 0 },
- },
- {
- key: 10000,
- anomaly_score: { value: 90 },
- },
- {
- key: 15000,
- anomaly_score: { value: 0 },
- },
- {
- key: 20000,
- anomaly_score: { value: 90 },
- },
- ]);
-
- const getMlBucketSize = 5;
- const bucketSize = 5;
- const timeSeriesDates = [5000, 15000];
- const anomalySeries = anomalySeriesTransform(
- esResponse,
- getMlBucketSize,
- bucketSize,
- timeSeriesDates
- );
-
- const buckets = anomalySeries!.anomalyScore;
- expect(buckets).toEqual([{ x0: 10000, x: 15000 }]);
- });
-
- it('should decrease the x-value to avoid going beyond last date', () => {
- const esResponse = getESResponse([
- {
- key: 0,
- anomaly_score: { value: 0 },
- },
- {
- key: 5000,
- anomaly_score: { value: 90 },
- },
- ]);
-
- const getMlBucketSize = 10;
- const bucketSize = 5;
- const timeSeriesDates = [0, 10000];
- const anomalySeries = anomalySeriesTransform(
- esResponse,
- getMlBucketSize,
- bucketSize,
- timeSeriesDates
- );
-
- const buckets = anomalySeries!.anomalyScore;
- expect(buckets).toEqual([{ x0: 5000, x: 10000 }]);
- });
- });
-
- describe('anomalyBoundariesSeries', () => {
- it('should trim buckets to time range', () => {
- const esResponse = getESResponse([
- {
- key: 0,
- upper: { value: 15 },
- lower: { value: 10 },
- },
- {
- key: 5000,
- upper: { value: 25 },
- lower: { value: 20 },
- },
- {
- key: 10000,
- upper: { value: 35 },
- lower: { value: 30 },
- },
- {
- key: 15000,
- upper: { value: 45 },
- lower: { value: 40 },
- },
- ]);
-
- const mlBucketSize = 10;
- const bucketSize = 5;
- const timeSeriesDates = [5000, 10000];
- const anomalySeries = anomalySeriesTransform(
- esResponse,
- mlBucketSize,
- bucketSize,
- timeSeriesDates
- );
-
- const buckets = anomalySeries!.anomalyBoundaries;
- expect(buckets).toEqual([
- { x: 5000, y: 25, y0: 20 },
- { x: 10000, y: 35, y0: 30 },
- ]);
- });
-
- it('should replace first bucket in range', () => {
- const esResponse = getESResponse([
- {
- key: 0,
- anomaly_score: { value: 0 },
- upper: { value: 15 },
- lower: { value: 10 },
- },
- {
- key: 5000,
- anomaly_score: { value: 0 },
- upper: { value: null },
- lower: { value: null },
- },
- {
- key: 10000,
- anomaly_score: { value: 0 },
- upper: { value: 25 },
- lower: { value: 20 },
- },
- ]);
-
- const getMlBucketSize = 10;
- const bucketSize = 5;
- const timeSeriesDates = [5000, 10000];
- const anomalySeries = anomalySeriesTransform(
- esResponse,
- getMlBucketSize,
- bucketSize,
- timeSeriesDates
- );
-
- const buckets = anomalySeries!.anomalyBoundaries;
- expect(buckets).toEqual([
- { x: 5000, y: 15, y0: 10 },
- { x: 10000, y: 25, y0: 20 },
- ]);
- });
-
- it('should replace last bucket in range', () => {
- const esResponse = getESResponse([
- {
- key: 0,
- anomaly_score: { value: 0 },
- upper: { value: 15 },
- lower: { value: 10 },
- },
- {
- key: 5000,
- anomaly_score: { value: 0 },
- upper: { value: null },
- lower: { value: null },
- },
- {
- key: 10000,
- anomaly_score: { value: 0 },
- upper: { value: null },
- lower: { value: null },
- },
- ]);
-
- const getMlBucketSize = 10;
- const bucketSize = 5;
- const timeSeriesDates = [5000, 10000];
- const anomalySeries = anomalySeriesTransform(
- esResponse,
- getMlBucketSize,
- bucketSize,
- timeSeriesDates
- );
-
- const buckets = anomalySeries!.anomalyBoundaries;
- expect(buckets).toEqual([
- { x: 5000, y: 15, y0: 10 },
- { x: 10000, y: 15, y0: 10 },
- ]);
- });
- });
-});
-
-describe('replaceFirstAndLastBucket', () => {
- it('should extend the first bucket', () => {
- const buckets = [
- {
- x: 0,
- lower: 10,
- upper: 20,
- },
- {
- x: 5,
- lower: null,
- upper: null,
- },
- {
- x: 10,
- lower: null,
- upper: null,
- },
- {
- x: 15,
- lower: 30,
- upper: 40,
- },
- ];
-
- const timeSeriesDates = [10, 15];
- expect(replaceFirstAndLastBucket(buckets as any, timeSeriesDates)).toEqual([
- { x: 10, lower: 10, upper: 20 },
- { x: 15, lower: 30, upper: 40 },
- ]);
- });
-
- it('should extend the last bucket', () => {
- const buckets = [
- {
- x: 10,
- lower: 30,
- upper: 40,
- },
- {
- x: 15,
- lower: null,
- upper: null,
- },
- {
- x: 20,
- lower: null,
- upper: null,
- },
- ] as any;
-
- const timeSeriesDates = [10, 15, 20];
- expect(replaceFirstAndLastBucket(buckets, timeSeriesDates)).toEqual([
- { x: 10, lower: 30, upper: 40 },
- { x: 15, lower: null, upper: null },
- { x: 20, lower: 30, upper: 40 },
- ]);
- });
-});
-
-function getESResponse(buckets: any): ESResponse {
- return ({
- took: 3,
- timed_out: false,
- _shards: {
- total: 5,
- successful: 5,
- skipped: 0,
- failed: 0,
- },
- hits: {
- total: 10,
- max_score: 0,
- hits: [],
- },
- aggregations: {
- ml_avg_response_times: {
- buckets: buckets.map((bucket: any) => {
- return {
- ...bucket,
- lower: { value: bucket?.lower?.value || null },
- upper: { value: bucket?.upper?.value || null },
- anomaly_score: {
- value: bucket?.anomaly_score?.value || null,
- },
- };
- }),
- },
- },
- } as unknown) as ESResponse;
-}
diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts
deleted file mode 100644
index 454a6add3e256..0000000000000
--- a/x-pack/plugins/apm/server/lib/transactions/charts/get_anomaly_data/transform.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { first, last } from 'lodash';
-import { Coordinate, RectCoordinate } from '../../../../../typings/timeseries';
-import { ESResponse } from './fetcher';
-
-type IBucket = ReturnType;
-function getBucket(
- bucket: Required<
- ESResponse
- >['aggregations']['ml_avg_response_times']['buckets'][0]
-) {
- return {
- x: bucket.key,
- anomalyScore: bucket.anomaly_score.value,
- lower: bucket.lower.value,
- upper: bucket.upper.value,
- };
-}
-
-export type AnomalyTimeSeriesResponse = ReturnType<
- typeof anomalySeriesTransform
->;
-export function anomalySeriesTransform(
- response: ESResponse,
- mlBucketSize: number,
- bucketSize: number,
- timeSeriesDates: number[]
-) {
- const buckets =
- response.aggregations?.ml_avg_response_times.buckets.map(getBucket) || [];
-
- const bucketSizeInMillis = Math.max(bucketSize, mlBucketSize) * 1000;
-
- return {
- anomalyScore: getAnomalyScoreDataPoints(
- buckets,
- timeSeriesDates,
- bucketSizeInMillis
- ),
- anomalyBoundaries: getAnomalyBoundaryDataPoints(buckets, timeSeriesDates),
- };
-}
-
-export function getAnomalyScoreDataPoints(
- buckets: IBucket[],
- timeSeriesDates: number[],
- bucketSizeInMillis: number
-): RectCoordinate[] {
- const ANOMALY_THRESHOLD = 75;
- const firstDate = first(timeSeriesDates);
- const lastDate = last(timeSeriesDates);
-
- return buckets
- .filter(
- (bucket) =>
- bucket.anomalyScore !== null && bucket.anomalyScore > ANOMALY_THRESHOLD
- )
- .filter(isInDateRange(firstDate, lastDate))
- .map((bucket) => {
- return {
- x0: bucket.x,
- x: Math.min(bucket.x + bucketSizeInMillis, lastDate), // don't go beyond last date
- };
- });
-}
-
-export function getAnomalyBoundaryDataPoints(
- buckets: IBucket[],
- timeSeriesDates: number[]
-): Coordinate[] {
- return replaceFirstAndLastBucket(buckets, timeSeriesDates)
- .filter((bucket) => bucket.lower !== null)
- .map((bucket) => {
- return {
- x: bucket.x,
- y0: bucket.lower,
- y: bucket.upper,
- };
- });
-}
-
-export function replaceFirstAndLastBucket(
- buckets: IBucket[],
- timeSeriesDates: number[]
-) {
- const firstDate = first(timeSeriesDates);
- const lastDate = last(timeSeriesDates);
-
- const preBucketWithValue = buckets
- .filter((p) => p.x <= firstDate)
- .reverse()
- .find((p) => p.lower !== null);
-
- const bucketsInRange = buckets.filter(isInDateRange(firstDate, lastDate));
-
- // replace first bucket if it is null
- const firstBucket = first(bucketsInRange);
- if (preBucketWithValue && firstBucket && firstBucket.lower === null) {
- firstBucket.lower = preBucketWithValue.lower;
- firstBucket.upper = preBucketWithValue.upper;
- }
-
- const lastBucketWithValue = [...buckets]
- .reverse()
- .find((p) => p.lower !== null);
-
- // replace last bucket if it is null
- const lastBucket = last(bucketsInRange);
- if (lastBucketWithValue && lastBucket && lastBucket.lower === null) {
- lastBucket.lower = lastBucketWithValue.lower;
- lastBucket.upper = lastBucketWithValue.upper;
- }
-
- return bucketsInRange;
-}
-
-// anomaly time series contain one or more buckets extra in the beginning
-// these extra buckets should be removed
-function isInDateRange(firstDate: number, lastDate: number) {
- return (p: IBucket) => p.x >= firstDate && p.x <= lastDate;
-}
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index c8a8027fb8bb4..bda29c0135ebe 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -4300,21 +4300,6 @@
"xpack.apm.serviceDetails.alertsMenu.errorRate": "エラー率",
"xpack.apm.serviceDetails.alertsMenu.transactionDuration": "トランザクション期間",
"xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts": "アクティブアラートを表示",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription": "現在 {serviceName} ({transactionType}) の実行中のジョブがあります。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription.viewJobLinkText": "既存のジョブを表示",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsTitle": "ジョブが既に存在します",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createMLJobDescription.transactionDurationGraphText": "トランザクション時間のグラフ",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createNewJobButtonLabel": "ジョブを作成",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.enableAnomalyDetectionTitle": "異常検知を有効にする",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText": "現在 {serviceName} ({transactionType}) の分析を実行中です。応答時間グラフに結果が追加されるまで少し時間がかかる場合があります。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText.viewJobLinkText": "ジョブを表示",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationTitle": "ジョブが作成されました",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreationFailedNotificationText": "現在のライセンスでは機械学習ジョブの作成が許可されていないか、ジョブが既に存在する可能性があります。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreationFailedNotificationTitle": "ジョブの作成に失敗",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.manageMLJobDescription": "ジョブはそれぞれのサービス + トランザクションタイプの組み合わせに対して作成できます。ジョブの作成後、{mlJobsPageLink} で管理と詳細の確認ができます。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.manageMLJobDescription.mlJobsPageLinkText": "機械学習ジョブの管理ページ",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.manageMLJobDescription.noteText": "注:ジョブが結果の計算を開始するまでに少し時間がかかる場合があります。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.selectTransactionTypeLabel": "このジョブのトランザクションタイプを選択してください",
"xpack.apm.serviceDetails.enableErrorReportsPanel.actionsDescription": "レポートはメールで送信するか Slack チャンネルに投稿できます。各レポートにはオカランス別のトップ 10 のエラーが含まれます。",
"xpack.apm.serviceDetails.enableErrorReportsPanel.actionsTitle": "アクション",
"xpack.apm.serviceDetails.enableErrorReportsPanel.conditionTitle": "コンディション",
@@ -4350,8 +4335,6 @@
"xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationText": "ユーザーにウォッチ作成のパーミッションがあることを確認してください。",
"xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationTitle": "ウォッチの作成に失敗",
"xpack.apm.serviceDetails.errorsTabLabel": "エラー",
- "xpack.apm.serviceDetails.integrationsMenu.enableMLAnomalyDetectionButtonLabel": "ML 異常検知を有効にする",
- "xpack.apm.serviceDetails.integrationsMenu.enableMLAnomalyDetectionButtonTooltip": "このサービスの機械学習ジョブをセットアップします",
"xpack.apm.serviceDetails.integrationsMenu.enableWatcherErrorReportsButtonLabel": "ウォッチエラーレポートを有効にする",
"xpack.apm.serviceDetails.integrationsMenu.integrationsButtonLabel": "統合",
"xpack.apm.serviceDetails.integrationsMenu.viewWatchesButtonLabel": "既存のウォッチを表示",
@@ -4361,9 +4344,6 @@
"xpack.apm.serviceDetails.metricsTabLabel": "メトリック",
"xpack.apm.serviceDetails.nodesTabLabel": "JVM",
"xpack.apm.serviceDetails.transactionsTabLabel": "トランザクション",
- "xpack.apm.serviceMap.anomalyDetectionPopoverLink": "異常を表示",
- "xpack.apm.serviceMap.anomalyDetectionPopoverScoreMetric": "スコア(最大)",
- "xpack.apm.serviceMap.anomalyDetectionPopoverTitle": "異常検知",
"xpack.apm.serviceMap.avgCpuUsagePopoverMetric": "CPU使用状況 (平均)",
"xpack.apm.serviceMap.avgErrorsPerMinutePopoverMetric": "1分あたりのエラー(平均)",
"xpack.apm.serviceMap.avgMemoryUsagePopoverMetric": "メモリー使用状況(平均)",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 04eb5a0d4c172..3f53130d00e75 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -4303,21 +4303,6 @@
"xpack.apm.serviceDetails.alertsMenu.errorRate": "错误率",
"xpack.apm.serviceDetails.alertsMenu.transactionDuration": "事务持续时间",
"xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts": "查看活动的告警",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription": "当前有 {serviceName}({transactionType})的作业正在运行。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription.viewJobLinkText": "查看现有作业",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsTitle": "作业已存在",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createMLJobDescription.transactionDurationGraphText": "事务持续时间图表",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createNewJobButtonLabel": "创建作业",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.enableAnomalyDetectionTitle": "启用异常检测",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText": "现在正在运行对 {serviceName}({transactionType})的分析。可能要花费点时间,才会将结果添加响应时间图表。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText.viewJobLinkText": "查看作业",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationTitle": "作业已成功创建",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreationFailedNotificationText": "您当前的许可可能不允许创建 Machine Learning 作业,或者此作业可能已存在。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreationFailedNotificationTitle": "作业创建失败",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.manageMLJobDescription": "可以创建每个服务 + 事务类型组合的作业。创建作业后,可以在 {mlJobsPageLink}中管理作业以及查看更多详细信息。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.manageMLJobDescription.mlJobsPageLinkText": "Machine Learning 作业管理页面",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.manageMLJobDescription.noteText": "注意:可能要过几分钟后,作业才会开始计算结果。",
- "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.selectTransactionTypeLabel": "为此作业选择事务类型",
"xpack.apm.serviceDetails.enableErrorReportsPanel.actionsDescription": "可以通过电子邮件发送报告或将报告发布到 Slack 频道。每个报告将包括按发生次数排序的前 10 个错误。",
"xpack.apm.serviceDetails.enableErrorReportsPanel.actionsTitle": "操作",
"xpack.apm.serviceDetails.enableErrorReportsPanel.conditionTitle": "条件",
@@ -4353,8 +4338,6 @@
"xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationText": "确保您的用户有权创建监视。",
"xpack.apm.serviceDetails.enableErrorReportsPanel.watchCreationFailedNotificationTitle": "监视创建失败",
"xpack.apm.serviceDetails.errorsTabLabel": "错误",
- "xpack.apm.serviceDetails.integrationsMenu.enableMLAnomalyDetectionButtonLabel": "启用 ML 异常检测",
- "xpack.apm.serviceDetails.integrationsMenu.enableMLAnomalyDetectionButtonTooltip": "为此服务设置 Machine Learning 作业",
"xpack.apm.serviceDetails.integrationsMenu.enableWatcherErrorReportsButtonLabel": "启用 Watcher 错误报告",
"xpack.apm.serviceDetails.integrationsMenu.integrationsButtonLabel": "集成",
"xpack.apm.serviceDetails.integrationsMenu.viewWatchesButtonLabel": "查看现有监视",
@@ -4364,9 +4347,6 @@
"xpack.apm.serviceDetails.metricsTabLabel": "指标",
"xpack.apm.serviceDetails.nodesTabLabel": "JVM",
"xpack.apm.serviceDetails.transactionsTabLabel": "事务",
- "xpack.apm.serviceMap.anomalyDetectionPopoverLink": "查看异常",
- "xpack.apm.serviceMap.anomalyDetectionPopoverScoreMetric": "分数(最大)",
- "xpack.apm.serviceMap.anomalyDetectionPopoverTitle": "异常检测",
"xpack.apm.serviceMap.avgCpuUsagePopoverMetric": "CPU 使用(平均)",
"xpack.apm.serviceMap.avgErrorsPerMinutePopoverMetric": "每分钟错误数(平均)",
"xpack.apm.serviceMap.avgMemoryUsagePopoverMetric": "内存使用(平均)",
From 000c14ed19c244fcbcf1a67e3007d82fd4e14d5b Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Wed, 24 Jun 2020 19:19:31 -0400
Subject: [PATCH 14/82] [7.x] Convert Positionable, RenderToDom and
RenderWithFn to functional/hooks/no recompose. (#68202) (#69855)
Co-authored-by: Elastic Machine
# Conflicts:
# x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.js
---
.../element_content/element_content.js | 12 +-
.../element_wrapper/element_wrapper.js | 8 +-
.../components/element_wrapper/index.js | 4 +-
.../element_wrapper/lib/handlers.js | 60 ---------
.../positionable/{index.js => index.ts} | 5 +-
.../components/positionable/positionable.js | 42 -------
.../components/positionable/positionable.tsx | 48 +++++++
.../public/components/render_to_dom/index.js | 12 --
.../handlers.js => render_to_dom/index.ts} | 14 +--
.../components/render_to_dom/render_to_dom.js | 40 ------
.../render_to_dom/render_to_dom.tsx | 27 ++++
.../public/components/render_with_fn/index.js | 30 -----
.../public/components/render_with_fn/index.ts | 7 ++
.../render_with_fn/render_with_fn.tsx | 117 ++++++++++++++++++
.../canvas/public/lib/create_handlers.ts | 96 ++++++++++++++
.../components/rendered_element.tsx | 14 +--
x-pack/plugins/canvas/types/renderers.ts | 28 +++--
17 files changed, 331 insertions(+), 233 deletions(-)
delete mode 100644 x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js
rename x-pack/plugins/canvas/public/components/positionable/{index.js => index.ts} (63%)
delete mode 100644 x-pack/plugins/canvas/public/components/positionable/positionable.js
create mode 100644 x-pack/plugins/canvas/public/components/positionable/positionable.tsx
delete mode 100644 x-pack/plugins/canvas/public/components/render_to_dom/index.js
rename x-pack/plugins/canvas/public/components/{render_with_fn/lib/handlers.js => render_to_dom/index.ts} (61%)
delete mode 100644 x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js
create mode 100644 x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.tsx
delete mode 100644 x-pack/plugins/canvas/public/components/render_with_fn/index.js
create mode 100644 x-pack/plugins/canvas/public/components/render_with_fn/index.ts
create mode 100644 x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
create mode 100644 x-pack/plugins/canvas/public/lib/create_handlers.ts
diff --git a/x-pack/plugins/canvas/public/components/element_content/element_content.js b/x-pack/plugins/canvas/public/components/element_content/element_content.js
index 114a457d167e7..e2c1a61c348d1 100644
--- a/x-pack/plugins/canvas/public/components/element_content/element_content.js
+++ b/x-pack/plugins/canvas/public/components/element_content/element_content.js
@@ -12,6 +12,7 @@ import { getType } from '@kbn/interpreter/common';
import { Loading } from '../loading';
import { RenderWithFn } from '../render_with_fn';
import { ElementShareContainer } from '../element_share_container';
+import { assignHandlers } from '../../lib/create_handlers';
import { InvalidExpression } from './invalid_expression';
import { InvalidElementType } from './invalid_element_type';
@@ -46,7 +47,7 @@ const branches = [
export const ElementContent = compose(
pure,
...branches
-)(({ renderable, renderFunction, size, handlers }) => {
+)(({ renderable, renderFunction, width, height, handlers }) => {
const {
getFilter,
setFilter,
@@ -62,7 +63,7 @@ export const ElementContent = compose(
diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js b/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js
index 845fc5927d839..de7748413b718 100644
--- a/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js
+++ b/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js
@@ -14,7 +14,13 @@ export const ElementWrapper = (props) => {
return (
-
+
);
};
diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/index.js b/x-pack/plugins/canvas/public/components/element_wrapper/index.js
index 390c349ab2ee6..6fc582bfee444 100644
--- a/x-pack/plugins/canvas/public/components/element_wrapper/index.js
+++ b/x-pack/plugins/canvas/public/components/element_wrapper/index.js
@@ -10,12 +10,12 @@ import { compose, withPropsOnChange, mapProps } from 'recompose';
import isEqual from 'react-fast-compare';
import { getResolvedArgs, getSelectedPage } from '../../state/selectors/workpad';
import { getState, getValue } from '../../lib/resolved_arg';
+import { createDispatchedHandlerFactory } from '../../lib/create_handlers';
import { ElementWrapper as Component } from './element_wrapper';
-import { createHandlers as createHandlersWithDispatch } from './lib/handlers';
function selectorFactory(dispatch) {
let result = {};
- const createHandlers = createHandlersWithDispatch(dispatch);
+ const createHandlers = createDispatchedHandlerFactory(dispatch);
return (nextState, nextOwnProps) => {
const { element, ...restOwnProps } = nextOwnProps;
diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js b/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js
deleted file mode 100644
index 33e8eacd902dd..0000000000000
--- a/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { isEqual } from 'lodash';
-import { setFilter } from '../../../state/actions/elements';
-import {
- updateEmbeddableExpression,
- fetchEmbeddableRenderable,
-} from '../../../state/actions/embeddable';
-
-export const createHandlers = (dispatch) => {
- let isComplete = false;
- let oldElement;
- let completeFn = () => {};
-
- return (element) => {
- // reset isComplete when element changes
- if (!isEqual(oldElement, element)) {
- isComplete = false;
- oldElement = element;
- }
-
- return {
- setFilter(text) {
- dispatch(setFilter(text, element.id, true));
- },
-
- getFilter() {
- return element.filter;
- },
-
- onComplete(fn) {
- completeFn = fn;
- },
-
- getElementId: () => element.id,
-
- onEmbeddableInputChange(embeddableExpression) {
- dispatch(updateEmbeddableExpression({ elementId: element.id, embeddableExpression }));
- },
-
- onEmbeddableDestroyed() {
- dispatch(fetchEmbeddableRenderable(element.id));
- },
-
- done() {
- // don't emit if the element is already done
- if (isComplete) {
- return;
- }
-
- isComplete = true;
- completeFn();
- },
- };
- };
-};
diff --git a/x-pack/plugins/canvas/public/components/positionable/index.js b/x-pack/plugins/canvas/public/components/positionable/index.ts
similarity index 63%
rename from x-pack/plugins/canvas/public/components/positionable/index.js
rename to x-pack/plugins/canvas/public/components/positionable/index.ts
index e5c3c32acb024..964e2ee41df75 100644
--- a/x-pack/plugins/canvas/public/components/positionable/index.js
+++ b/x-pack/plugins/canvas/public/components/positionable/index.ts
@@ -4,7 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { pure } from 'recompose';
-import { Positionable as Component } from './positionable';
-
-export const Positionable = pure(Component);
+export { Positionable } from './positionable';
diff --git a/x-pack/plugins/canvas/public/components/positionable/positionable.js b/x-pack/plugins/canvas/public/components/positionable/positionable.js
deleted file mode 100644
index 9898f50cbb0f0..0000000000000
--- a/x-pack/plugins/canvas/public/components/positionable/positionable.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { matrixToCSS } from '../../lib/dom';
-
-export const Positionable = ({ children, transformMatrix, width, height }) => {
- // Throw if there is more than one child
- React.Children.only(children);
- // This could probably be made nicer by having just one child
- const wrappedChildren = React.Children.map(children, (child) => {
- const newStyle = {
- width,
- height,
- marginLeft: -width / 2,
- marginTop: -height / 2,
- position: 'absolute',
- transform: matrixToCSS(transformMatrix.map((n, i) => (i < 12 ? n : Math.round(n)))),
- };
-
- const stepChild = React.cloneElement(child, { size: { width, height } });
- return (
-
- {stepChild}
-
- );
- });
-
- return wrappedChildren;
-};
-
-Positionable.propTypes = {
- onChange: PropTypes.func,
- children: PropTypes.element.isRequired,
- transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired,
- width: PropTypes.number.isRequired,
- height: PropTypes.number.isRequired,
-};
diff --git a/x-pack/plugins/canvas/public/components/positionable/positionable.tsx b/x-pack/plugins/canvas/public/components/positionable/positionable.tsx
new file mode 100644
index 0000000000000..3344398b00198
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/positionable/positionable.tsx
@@ -0,0 +1,48 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, ReactElement, CSSProperties } from 'react';
+import PropTypes from 'prop-types';
+import { matrixToCSS } from '../../lib/dom';
+import { TransformMatrix3d } from '../../lib/aeroelastic';
+
+interface Props {
+ children: ReactElement;
+ transformMatrix: TransformMatrix3d;
+ height: number;
+ width: number;
+}
+
+export const Positionable: FC = ({ children, transformMatrix, width, height }) => {
+ // Throw if there is more than one child
+ const childNode = React.Children.only(children);
+
+ const matrix = (transformMatrix.map((n, i) =>
+ i < 12 ? n : Math.round(n)
+ ) as any) as TransformMatrix3d;
+
+ const newStyle: CSSProperties = {
+ width,
+ height,
+ marginLeft: -width / 2,
+ marginTop: -height / 2,
+ position: 'absolute',
+ transform: matrixToCSS(matrix),
+ };
+
+ return (
+
+ {childNode}
+
+ );
+};
+
+Positionable.propTypes = {
+ children: PropTypes.element.isRequired,
+ transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired,
+ width: PropTypes.number.isRequired,
+ height: PropTypes.number.isRequired,
+};
diff --git a/x-pack/plugins/canvas/public/components/render_to_dom/index.js b/x-pack/plugins/canvas/public/components/render_to_dom/index.js
deleted file mode 100644
index e8a3f8cd8c93b..0000000000000
--- a/x-pack/plugins/canvas/public/components/render_to_dom/index.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { compose, withState } from 'recompose';
-import { RenderToDom as Component } from './render_to_dom';
-
-export const RenderToDom = compose(
- withState('domNode', 'setDomNode') // Still don't like this, seems to be the only way todo it.
-)(Component);
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/lib/handlers.js b/x-pack/plugins/canvas/public/components/render_to_dom/index.ts
similarity index 61%
rename from x-pack/plugins/canvas/public/components/render_with_fn/lib/handlers.js
rename to x-pack/plugins/canvas/public/components/render_to_dom/index.ts
index 9e5032efa97e2..43a5dad059c95 100644
--- a/x-pack/plugins/canvas/public/components/render_with_fn/lib/handlers.js
+++ b/x-pack/plugins/canvas/public/components/render_to_dom/index.ts
@@ -4,16 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export class ElementHandlers {
- resize() {}
-
- destroy() {}
-
- onResize(fn) {
- this.resize = fn;
- }
-
- onDestroy(fn) {
- this.destroy = fn;
- }
-}
+export { RenderToDom } from './render_to_dom';
diff --git a/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js b/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js
deleted file mode 100644
index db393a8dde4f9..0000000000000
--- a/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export class RenderToDom extends React.Component {
- static propTypes = {
- domNode: PropTypes.object,
- setDomNode: PropTypes.func.isRequired,
- render: PropTypes.func.isRequired,
- style: PropTypes.object,
- };
-
- shouldComponentUpdate(nextProps) {
- return this.props.domNode !== nextProps.domNode;
- }
-
- componentDidUpdate() {
- // Calls render function once we have the reference to the DOM element to render into
- if (this.props.domNode) {
- this.props.render(this.props.domNode);
- }
- }
-
- render() {
- const { domNode, setDomNode, style } = this.props;
- const linkRef = (refNode) => {
- if (!domNode && refNode) {
- // Initialize the domNode property. This should only happen once, even if config changes.
- setDomNode(refNode);
- }
- };
-
- return
;
- }
-}
diff --git a/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.tsx b/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.tsx
new file mode 100644
index 0000000000000..a37c0fc096e57
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.tsx
@@ -0,0 +1,27 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useCallback, FC } from 'react';
+import CSS from 'csstype';
+
+interface Props {
+ render: (element: HTMLElement) => void;
+ style?: CSS.Properties;
+}
+
+export const RenderToDom: FC = ({ render, style }) => {
+ // https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
+ const ref = useCallback(
+ (node: HTMLDivElement) => {
+ if (node !== null) {
+ render(node);
+ }
+ },
+ [render]
+ );
+
+ return
;
+};
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/index.js b/x-pack/plugins/canvas/public/components/render_with_fn/index.js
deleted file mode 100644
index 37c49624a3940..0000000000000
--- a/x-pack/plugins/canvas/public/components/render_with_fn/index.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { compose, withProps, withPropsOnChange } from 'recompose';
-import PropTypes from 'prop-types';
-import isEqual from 'react-fast-compare';
-import { withKibana } from '../../../../../../src/plugins/kibana_react/public';
-import { RenderWithFn as Component } from './render_with_fn';
-import { ElementHandlers } from './lib/handlers';
-
-export const RenderWithFn = compose(
- withPropsOnChange(
- // rebuild elementHandlers when handlers object changes
- (props, nextProps) => !isEqual(props.handlers, nextProps.handlers),
- ({ handlers }) => ({
- handlers: Object.assign(new ElementHandlers(), handlers),
- })
- ),
- withKibana,
- withProps((props) => ({
- onError: props.kibana.services.canvas.notify.error,
- }))
-)(Component);
-
-RenderWithFn.propTypes = {
- handlers: PropTypes.object,
-};
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/index.ts b/x-pack/plugins/canvas/public/components/render_with_fn/index.ts
new file mode 100644
index 0000000000000..4bfef734d34f4
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/render_with_fn/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { RenderWithFn } from './render_with_fn';
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
new file mode 100644
index 0000000000000..bc51128cf0c87
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
@@ -0,0 +1,117 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { useState, useEffect, useRef, FC, useCallback } from 'react';
+import { useDebounce } from 'react-use';
+
+import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
+import { RenderToDom } from '../render_to_dom';
+import { ErrorStrings } from '../../../i18n';
+import { RendererHandlers } from '../../../types';
+
+const { RenderWithFn: strings } = ErrorStrings;
+
+interface Props {
+ name: string;
+ renderFn: (
+ domNode: HTMLElement,
+ config: Record,
+ handlers: RendererHandlers
+ ) => void | Promise;
+ reuseNode: boolean;
+ handlers: RendererHandlers;
+ config: Record;
+ height: number;
+ width: number;
+}
+
+const style = { height: '100%', width: '100%' };
+
+export const RenderWithFn: FC = ({
+ name: functionName,
+ renderFn,
+ reuseNode = false,
+ handlers: incomingHandlers,
+ config,
+ width,
+ height,
+}) => {
+ const { services } = useKibana();
+ const onError = services.canvas.notify.error;
+
+ const [domNode, setDomNode] = useState(null);
+
+ // Tells us if the component is attempting to re-render into a previously-populated render target.
+ const firstRender = useRef(true);
+ // A reference to the node appended to the provided DOM node which is created and optionally replaced.
+ const renderTarget = useRef(null);
+ // A reference to the handlers, as the renderFn may mutate them, (via onXYZ functions)
+ const handlers = useRef(incomingHandlers);
+
+ // Reset the render target, the node appended to the DOM node provided by RenderToDOM.
+ const resetRenderTarget = useCallback(() => {
+ if (!domNode) {
+ return;
+ }
+
+ if (!firstRender) {
+ handlers.current.destroy();
+ }
+
+ while (domNode.firstChild) {
+ domNode.removeChild(domNode.firstChild);
+ }
+
+ const div = document.createElement('div');
+ div.style.width = '100%';
+ div.style.height = '100%';
+ domNode.appendChild(div);
+
+ renderTarget.current = div;
+ firstRender.current = true;
+ }, [domNode]);
+
+ useDebounce(() => handlers.current.resize({ height, width }), 150, [height, width]);
+
+ useEffect(
+ () => () => {
+ handlers.current.destroy();
+ },
+ []
+ );
+
+ const render = useCallback(() => {
+ renderFn(renderTarget.current!, config, handlers.current);
+ }, [renderTarget, config, renderFn]);
+
+ useEffect(() => {
+ if (!domNode) {
+ return;
+ }
+
+ if (!reuseNode || !renderTarget.current) {
+ resetRenderTarget();
+ }
+
+ try {
+ render();
+ firstRender.current = false;
+ } catch (err) {
+ onError(err, { title: strings.getRenderErrorMessage(functionName) });
+ }
+ }, [domNode, functionName, onError, render, resetRenderTarget, reuseNode]);
+
+ return (
+
+ {
+ setDomNode(node);
+ }}
+ />
+
+ );
+};
diff --git a/x-pack/plugins/canvas/public/lib/create_handlers.ts b/x-pack/plugins/canvas/public/lib/create_handlers.ts
new file mode 100644
index 0000000000000..4e0c7b217d5b7
--- /dev/null
+++ b/x-pack/plugins/canvas/public/lib/create_handlers.ts
@@ -0,0 +1,96 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { isEqual } from 'lodash';
+// @ts-ignore untyped local
+import { setFilter } from '../state/actions/elements';
+import { updateEmbeddableExpression, fetchEmbeddableRenderable } from '../state/actions/embeddable';
+import { RendererHandlers, CanvasElement } from '../../types';
+
+// This class creates stub handlers to ensure every element and renderer fulfills the contract.
+// TODO: consider warning if these methods are invoked but not implemented by the renderer...?
+
+export const createHandlers = (): RendererHandlers => ({
+ destroy() {},
+ done() {},
+ event() {},
+ getElementId() {
+ return '';
+ },
+ getFilter() {
+ return '';
+ },
+ onComplete(fn: () => void) {
+ this.done = fn;
+ },
+ onDestroy(fn: () => void) {
+ this.destroy = fn;
+ },
+ // TODO: these functions do not match the `onXYZ` and `xyz` pattern elsewhere.
+ onEmbeddableDestroyed() {},
+ onEmbeddableInputChange() {},
+ onResize(fn: (size: { height: number; width: number }) => void) {
+ this.resize = fn;
+ },
+ reload() {},
+ resize(_size: { height: number; width: number }) {},
+ setFilter() {},
+ update() {},
+});
+
+export const assignHandlers = (handlers: Partial = {}): RendererHandlers =>
+ Object.assign(createHandlers(), handlers);
+
+// TODO: this is a legacy approach we should unravel in the near future.
+export const createDispatchedHandlerFactory = (
+ dispatch: (action: any) => void
+): ((element: CanvasElement) => RendererHandlers) => {
+ let isComplete = false;
+ let oldElement: CanvasElement | undefined;
+ let completeFn = () => {};
+
+ return (element: CanvasElement) => {
+ // reset isComplete when element changes
+ if (!isEqual(oldElement, element)) {
+ isComplete = false;
+ oldElement = element;
+ }
+
+ return assignHandlers({
+ setFilter(text: string) {
+ dispatch(setFilter(text, element.id, true));
+ },
+
+ getFilter() {
+ return element.filter;
+ },
+
+ onComplete(fn: () => void) {
+ completeFn = fn;
+ },
+
+ getElementId: () => element.id,
+
+ onEmbeddableInputChange(embeddableExpression: string) {
+ dispatch(updateEmbeddableExpression({ elementId: element.id, embeddableExpression }));
+ },
+
+ onEmbeddableDestroyed() {
+ dispatch(fetchEmbeddableRenderable(element.id));
+ },
+
+ done() {
+ // don't emit if the element is already done
+ if (isComplete) {
+ return;
+ }
+
+ isComplete = true;
+ completeFn();
+ },
+ });
+ };
+};
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
index 5741f5f2d698c..6bcc0db92f1cc 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
+++ b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
@@ -7,13 +7,13 @@
import React, { FC, PureComponent } from 'react';
// @ts-expect-error untyped library
import Style from 'style-it';
-// @ts-expect-error untyped local
import { Positionable } from '../../public/components/positionable/positionable';
// @ts-expect-error untyped local
import { elementToShape } from '../../public/components/workpad_page/utils';
import { CanvasRenderedElement } from '../types';
import { CanvasShareableContext, useCanvasShareableState } from '../context';
import { RendererSpec } from '../../types';
+import { createHandlers } from '../../public/lib/create_handlers';
import css from './rendered_element.module.scss';
@@ -62,17 +62,7 @@ export class RenderedElementComponent extends PureComponent {
}
try {
- // TODO: These are stubbed, but may need implementation.
- fn.render(this.ref.current, value.value, {
- done: () => {},
- onDestroy: () => {},
- onResize: () => {},
- getElementId: () => '',
- setFilter: () => {},
- getFilter: () => '',
- onEmbeddableInputChange: () => {},
- onEmbeddableDestroyed: () => {},
- });
+ fn.render(this.ref.current, value.value, createHandlers());
} catch (e) {
// eslint-disable-next-line no-console
console.log(as, e.message);
diff --git a/x-pack/plugins/canvas/types/renderers.ts b/x-pack/plugins/canvas/types/renderers.ts
index 2564b045d1cf7..772a16aa94c60 100644
--- a/x-pack/plugins/canvas/types/renderers.ts
+++ b/x-pack/plugins/canvas/types/renderers.ts
@@ -4,25 +4,29 @@
* you may not use this file except in compliance with the Elastic License.
*/
-type GenericCallback = (callback: () => void) => void;
+import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
-export interface RendererHandlers {
- /** Handler to invoke when an element has finished rendering */
- done: () => void;
+type GenericRendererCallback = (callback: () => void) => void;
+
+export interface RendererHandlers extends IInterpreterRenderHandlers {
+ /** Handler to invoke when an element should be destroyed. */
+ destroy: () => void;
/** Get the id of the element being rendered. Can be used as a unique ID in a render function */
getElementId: () => string;
- /** Handler to invoke when an element is deleted or changes to a different render type */
- onDestroy: GenericCallback;
- /** Handler to invoke when an element's dimensions have changed*/
- onResize: GenericCallback;
/** Retrieves the value of the filter property on the element object persisted on the workpad */
getFilter: () => string;
- /** Sets the value of the filter property on the element object persisted on the workpad */
- setFilter: (filter: string) => void;
- /** Handler to invoke when the input to a function has changed internally */
- onEmbeddableInputChange: (expression: string) => void;
+ /** Handler to invoke when a renderer is considered complete */
+ onComplete: (fn: () => void) => void;
/** Handler to invoke when a rendered embeddable is destroyed */
onEmbeddableDestroyed: () => void;
+ /** Handler to invoke when the input to a function has changed internally */
+ onEmbeddableInputChange: (expression: string) => void;
+ /** Handler to invoke when an element's dimensions have changed*/
+ onResize: GenericRendererCallback;
+ /** Handler to invoke when an element should be resized. */
+ resize: (size: { height: number; width: number }) => void;
+ /** Sets the value of the filter property on the element object persisted on the workpad */
+ setFilter: (filter: string) => void;
}
export interface RendererSpec {
From 312db71c41a7bfb9144a2bba1650ce917180c72b Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Wed, 24 Jun 2020 19:21:38 -0400
Subject: [PATCH 15/82] Revert "[7.x] Convert Positionable, RenderToDom and
RenderWithFn to functional/hooks/no recompose. (#68202)" (#69865)
This reverts commit 000c14ed19c244fcbcf1a67e3007d82fd4e14d5b.
---
.../element_content/element_content.js | 12 +-
.../element_wrapper/element_wrapper.js | 8 +-
.../components/element_wrapper/index.js | 4 +-
.../element_wrapper/lib/handlers.js | 60 +++++++++
.../positionable/{index.ts => index.js} | 5 +-
.../components/positionable/positionable.js | 42 +++++++
.../components/positionable/positionable.tsx | 48 -------
.../public/components/render_to_dom/index.js | 12 ++
.../public/components/render_to_dom/index.ts | 7 --
.../components/render_to_dom/render_to_dom.js | 40 ++++++
.../render_to_dom/render_to_dom.tsx | 27 ----
.../public/components/render_with_fn/index.js | 30 +++++
.../{index.ts => lib/handlers.js} | 14 ++-
.../render_with_fn/render_with_fn.tsx | 117 ------------------
.../canvas/public/lib/create_handlers.ts | 96 --------------
.../components/rendered_element.tsx | 14 ++-
x-pack/plugins/canvas/types/renderers.ts | 28 ++---
17 files changed, 233 insertions(+), 331 deletions(-)
create mode 100644 x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js
rename x-pack/plugins/canvas/public/components/positionable/{index.ts => index.js} (63%)
create mode 100644 x-pack/plugins/canvas/public/components/positionable/positionable.js
delete mode 100644 x-pack/plugins/canvas/public/components/positionable/positionable.tsx
create mode 100644 x-pack/plugins/canvas/public/components/render_to_dom/index.js
delete mode 100644 x-pack/plugins/canvas/public/components/render_to_dom/index.ts
create mode 100644 x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js
delete mode 100644 x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.tsx
create mode 100644 x-pack/plugins/canvas/public/components/render_with_fn/index.js
rename x-pack/plugins/canvas/public/components/render_with_fn/{index.ts => lib/handlers.js} (61%)
delete mode 100644 x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
delete mode 100644 x-pack/plugins/canvas/public/lib/create_handlers.ts
diff --git a/x-pack/plugins/canvas/public/components/element_content/element_content.js b/x-pack/plugins/canvas/public/components/element_content/element_content.js
index e2c1a61c348d1..114a457d167e7 100644
--- a/x-pack/plugins/canvas/public/components/element_content/element_content.js
+++ b/x-pack/plugins/canvas/public/components/element_content/element_content.js
@@ -12,7 +12,6 @@ import { getType } from '@kbn/interpreter/common';
import { Loading } from '../loading';
import { RenderWithFn } from '../render_with_fn';
import { ElementShareContainer } from '../element_share_container';
-import { assignHandlers } from '../../lib/create_handlers';
import { InvalidExpression } from './invalid_expression';
import { InvalidElementType } from './invalid_element_type';
@@ -47,7 +46,7 @@ const branches = [
export const ElementContent = compose(
pure,
...branches
-)(({ renderable, renderFunction, width, height, handlers }) => {
+)(({ renderable, renderFunction, size, handlers }) => {
const {
getFilter,
setFilter,
@@ -63,7 +62,7 @@ export const ElementContent = compose(
diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js b/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js
index de7748413b718..845fc5927d839 100644
--- a/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js
+++ b/x-pack/plugins/canvas/public/components/element_wrapper/element_wrapper.js
@@ -14,13 +14,7 @@ export const ElementWrapper = (props) => {
return (
-
+
);
};
diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/index.js b/x-pack/plugins/canvas/public/components/element_wrapper/index.js
index 6fc582bfee444..390c349ab2ee6 100644
--- a/x-pack/plugins/canvas/public/components/element_wrapper/index.js
+++ b/x-pack/plugins/canvas/public/components/element_wrapper/index.js
@@ -10,12 +10,12 @@ import { compose, withPropsOnChange, mapProps } from 'recompose';
import isEqual from 'react-fast-compare';
import { getResolvedArgs, getSelectedPage } from '../../state/selectors/workpad';
import { getState, getValue } from '../../lib/resolved_arg';
-import { createDispatchedHandlerFactory } from '../../lib/create_handlers';
import { ElementWrapper as Component } from './element_wrapper';
+import { createHandlers as createHandlersWithDispatch } from './lib/handlers';
function selectorFactory(dispatch) {
let result = {};
- const createHandlers = createDispatchedHandlerFactory(dispatch);
+ const createHandlers = createHandlersWithDispatch(dispatch);
return (nextState, nextOwnProps) => {
const { element, ...restOwnProps } = nextOwnProps;
diff --git a/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js b/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js
new file mode 100644
index 0000000000000..33e8eacd902dd
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/element_wrapper/lib/handlers.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { isEqual } from 'lodash';
+import { setFilter } from '../../../state/actions/elements';
+import {
+ updateEmbeddableExpression,
+ fetchEmbeddableRenderable,
+} from '../../../state/actions/embeddable';
+
+export const createHandlers = (dispatch) => {
+ let isComplete = false;
+ let oldElement;
+ let completeFn = () => {};
+
+ return (element) => {
+ // reset isComplete when element changes
+ if (!isEqual(oldElement, element)) {
+ isComplete = false;
+ oldElement = element;
+ }
+
+ return {
+ setFilter(text) {
+ dispatch(setFilter(text, element.id, true));
+ },
+
+ getFilter() {
+ return element.filter;
+ },
+
+ onComplete(fn) {
+ completeFn = fn;
+ },
+
+ getElementId: () => element.id,
+
+ onEmbeddableInputChange(embeddableExpression) {
+ dispatch(updateEmbeddableExpression({ elementId: element.id, embeddableExpression }));
+ },
+
+ onEmbeddableDestroyed() {
+ dispatch(fetchEmbeddableRenderable(element.id));
+ },
+
+ done() {
+ // don't emit if the element is already done
+ if (isComplete) {
+ return;
+ }
+
+ isComplete = true;
+ completeFn();
+ },
+ };
+ };
+};
diff --git a/x-pack/plugins/canvas/public/components/positionable/index.ts b/x-pack/plugins/canvas/public/components/positionable/index.js
similarity index 63%
rename from x-pack/plugins/canvas/public/components/positionable/index.ts
rename to x-pack/plugins/canvas/public/components/positionable/index.js
index 964e2ee41df75..e5c3c32acb024 100644
--- a/x-pack/plugins/canvas/public/components/positionable/index.ts
+++ b/x-pack/plugins/canvas/public/components/positionable/index.js
@@ -4,4 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { Positionable } from './positionable';
+import { pure } from 'recompose';
+import { Positionable as Component } from './positionable';
+
+export const Positionable = pure(Component);
diff --git a/x-pack/plugins/canvas/public/components/positionable/positionable.js b/x-pack/plugins/canvas/public/components/positionable/positionable.js
new file mode 100644
index 0000000000000..9898f50cbb0f0
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/positionable/positionable.js
@@ -0,0 +1,42 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import { matrixToCSS } from '../../lib/dom';
+
+export const Positionable = ({ children, transformMatrix, width, height }) => {
+ // Throw if there is more than one child
+ React.Children.only(children);
+ // This could probably be made nicer by having just one child
+ const wrappedChildren = React.Children.map(children, (child) => {
+ const newStyle = {
+ width,
+ height,
+ marginLeft: -width / 2,
+ marginTop: -height / 2,
+ position: 'absolute',
+ transform: matrixToCSS(transformMatrix.map((n, i) => (i < 12 ? n : Math.round(n)))),
+ };
+
+ const stepChild = React.cloneElement(child, { size: { width, height } });
+ return (
+
+ {stepChild}
+
+ );
+ });
+
+ return wrappedChildren;
+};
+
+Positionable.propTypes = {
+ onChange: PropTypes.func,
+ children: PropTypes.element.isRequired,
+ transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired,
+ width: PropTypes.number.isRequired,
+ height: PropTypes.number.isRequired,
+};
diff --git a/x-pack/plugins/canvas/public/components/positionable/positionable.tsx b/x-pack/plugins/canvas/public/components/positionable/positionable.tsx
deleted file mode 100644
index 3344398b00198..0000000000000
--- a/x-pack/plugins/canvas/public/components/positionable/positionable.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React, { FC, ReactElement, CSSProperties } from 'react';
-import PropTypes from 'prop-types';
-import { matrixToCSS } from '../../lib/dom';
-import { TransformMatrix3d } from '../../lib/aeroelastic';
-
-interface Props {
- children: ReactElement;
- transformMatrix: TransformMatrix3d;
- height: number;
- width: number;
-}
-
-export const Positionable: FC = ({ children, transformMatrix, width, height }) => {
- // Throw if there is more than one child
- const childNode = React.Children.only(children);
-
- const matrix = (transformMatrix.map((n, i) =>
- i < 12 ? n : Math.round(n)
- ) as any) as TransformMatrix3d;
-
- const newStyle: CSSProperties = {
- width,
- height,
- marginLeft: -width / 2,
- marginTop: -height / 2,
- position: 'absolute',
- transform: matrixToCSS(matrix),
- };
-
- return (
-
- {childNode}
-
- );
-};
-
-Positionable.propTypes = {
- children: PropTypes.element.isRequired,
- transformMatrix: PropTypes.arrayOf(PropTypes.number).isRequired,
- width: PropTypes.number.isRequired,
- height: PropTypes.number.isRequired,
-};
diff --git a/x-pack/plugins/canvas/public/components/render_to_dom/index.js b/x-pack/plugins/canvas/public/components/render_to_dom/index.js
new file mode 100644
index 0000000000000..e8a3f8cd8c93b
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/render_to_dom/index.js
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { compose, withState } from 'recompose';
+import { RenderToDom as Component } from './render_to_dom';
+
+export const RenderToDom = compose(
+ withState('domNode', 'setDomNode') // Still don't like this, seems to be the only way todo it.
+)(Component);
diff --git a/x-pack/plugins/canvas/public/components/render_to_dom/index.ts b/x-pack/plugins/canvas/public/components/render_to_dom/index.ts
deleted file mode 100644
index 43a5dad059c95..0000000000000
--- a/x-pack/plugins/canvas/public/components/render_to_dom/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-export { RenderToDom } from './render_to_dom';
diff --git a/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js b/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js
new file mode 100644
index 0000000000000..db393a8dde4f9
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.js
@@ -0,0 +1,40 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+
+export class RenderToDom extends React.Component {
+ static propTypes = {
+ domNode: PropTypes.object,
+ setDomNode: PropTypes.func.isRequired,
+ render: PropTypes.func.isRequired,
+ style: PropTypes.object,
+ };
+
+ shouldComponentUpdate(nextProps) {
+ return this.props.domNode !== nextProps.domNode;
+ }
+
+ componentDidUpdate() {
+ // Calls render function once we have the reference to the DOM element to render into
+ if (this.props.domNode) {
+ this.props.render(this.props.domNode);
+ }
+ }
+
+ render() {
+ const { domNode, setDomNode, style } = this.props;
+ const linkRef = (refNode) => {
+ if (!domNode && refNode) {
+ // Initialize the domNode property. This should only happen once, even if config changes.
+ setDomNode(refNode);
+ }
+ };
+
+ return
;
+ }
+}
diff --git a/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.tsx b/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.tsx
deleted file mode 100644
index a37c0fc096e57..0000000000000
--- a/x-pack/plugins/canvas/public/components/render_to_dom/render_to_dom.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React, { useCallback, FC } from 'react';
-import CSS from 'csstype';
-
-interface Props {
- render: (element: HTMLElement) => void;
- style?: CSS.Properties;
-}
-
-export const RenderToDom: FC = ({ render, style }) => {
- // https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node
- const ref = useCallback(
- (node: HTMLDivElement) => {
- if (node !== null) {
- render(node);
- }
- },
- [render]
- );
-
- return
;
-};
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/index.js b/x-pack/plugins/canvas/public/components/render_with_fn/index.js
new file mode 100644
index 0000000000000..37c49624a3940
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/render_with_fn/index.js
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { compose, withProps, withPropsOnChange } from 'recompose';
+import PropTypes from 'prop-types';
+import isEqual from 'react-fast-compare';
+import { withKibana } from '../../../../../../src/plugins/kibana_react/public';
+import { RenderWithFn as Component } from './render_with_fn';
+import { ElementHandlers } from './lib/handlers';
+
+export const RenderWithFn = compose(
+ withPropsOnChange(
+ // rebuild elementHandlers when handlers object changes
+ (props, nextProps) => !isEqual(props.handlers, nextProps.handlers),
+ ({ handlers }) => ({
+ handlers: Object.assign(new ElementHandlers(), handlers),
+ })
+ ),
+ withKibana,
+ withProps((props) => ({
+ onError: props.kibana.services.canvas.notify.error,
+ }))
+)(Component);
+
+RenderWithFn.propTypes = {
+ handlers: PropTypes.object,
+};
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/index.ts b/x-pack/plugins/canvas/public/components/render_with_fn/lib/handlers.js
similarity index 61%
rename from x-pack/plugins/canvas/public/components/render_with_fn/index.ts
rename to x-pack/plugins/canvas/public/components/render_with_fn/lib/handlers.js
index 4bfef734d34f4..9e5032efa97e2 100644
--- a/x-pack/plugins/canvas/public/components/render_with_fn/index.ts
+++ b/x-pack/plugins/canvas/public/components/render_with_fn/lib/handlers.js
@@ -4,4 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { RenderWithFn } from './render_with_fn';
+export class ElementHandlers {
+ resize() {}
+
+ destroy() {}
+
+ onResize(fn) {
+ this.resize = fn;
+ }
+
+ onDestroy(fn) {
+ this.destroy = fn;
+ }
+}
diff --git a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx b/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
deleted file mode 100644
index bc51128cf0c87..0000000000000
--- a/x-pack/plugins/canvas/public/components/render_with_fn/render_with_fn.tsx
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React, { useState, useEffect, useRef, FC, useCallback } from 'react';
-import { useDebounce } from 'react-use';
-
-import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
-import { RenderToDom } from '../render_to_dom';
-import { ErrorStrings } from '../../../i18n';
-import { RendererHandlers } from '../../../types';
-
-const { RenderWithFn: strings } = ErrorStrings;
-
-interface Props {
- name: string;
- renderFn: (
- domNode: HTMLElement,
- config: Record,
- handlers: RendererHandlers
- ) => void | Promise;
- reuseNode: boolean;
- handlers: RendererHandlers;
- config: Record;
- height: number;
- width: number;
-}
-
-const style = { height: '100%', width: '100%' };
-
-export const RenderWithFn: FC = ({
- name: functionName,
- renderFn,
- reuseNode = false,
- handlers: incomingHandlers,
- config,
- width,
- height,
-}) => {
- const { services } = useKibana();
- const onError = services.canvas.notify.error;
-
- const [domNode, setDomNode] = useState(null);
-
- // Tells us if the component is attempting to re-render into a previously-populated render target.
- const firstRender = useRef(true);
- // A reference to the node appended to the provided DOM node which is created and optionally replaced.
- const renderTarget = useRef(null);
- // A reference to the handlers, as the renderFn may mutate them, (via onXYZ functions)
- const handlers = useRef(incomingHandlers);
-
- // Reset the render target, the node appended to the DOM node provided by RenderToDOM.
- const resetRenderTarget = useCallback(() => {
- if (!domNode) {
- return;
- }
-
- if (!firstRender) {
- handlers.current.destroy();
- }
-
- while (domNode.firstChild) {
- domNode.removeChild(domNode.firstChild);
- }
-
- const div = document.createElement('div');
- div.style.width = '100%';
- div.style.height = '100%';
- domNode.appendChild(div);
-
- renderTarget.current = div;
- firstRender.current = true;
- }, [domNode]);
-
- useDebounce(() => handlers.current.resize({ height, width }), 150, [height, width]);
-
- useEffect(
- () => () => {
- handlers.current.destroy();
- },
- []
- );
-
- const render = useCallback(() => {
- renderFn(renderTarget.current!, config, handlers.current);
- }, [renderTarget, config, renderFn]);
-
- useEffect(() => {
- if (!domNode) {
- return;
- }
-
- if (!reuseNode || !renderTarget.current) {
- resetRenderTarget();
- }
-
- try {
- render();
- firstRender.current = false;
- } catch (err) {
- onError(err, { title: strings.getRenderErrorMessage(functionName) });
- }
- }, [domNode, functionName, onError, render, resetRenderTarget, reuseNode]);
-
- return (
-
- {
- setDomNode(node);
- }}
- />
-
- );
-};
diff --git a/x-pack/plugins/canvas/public/lib/create_handlers.ts b/x-pack/plugins/canvas/public/lib/create_handlers.ts
deleted file mode 100644
index 4e0c7b217d5b7..0000000000000
--- a/x-pack/plugins/canvas/public/lib/create_handlers.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { isEqual } from 'lodash';
-// @ts-ignore untyped local
-import { setFilter } from '../state/actions/elements';
-import { updateEmbeddableExpression, fetchEmbeddableRenderable } from '../state/actions/embeddable';
-import { RendererHandlers, CanvasElement } from '../../types';
-
-// This class creates stub handlers to ensure every element and renderer fulfills the contract.
-// TODO: consider warning if these methods are invoked but not implemented by the renderer...?
-
-export const createHandlers = (): RendererHandlers => ({
- destroy() {},
- done() {},
- event() {},
- getElementId() {
- return '';
- },
- getFilter() {
- return '';
- },
- onComplete(fn: () => void) {
- this.done = fn;
- },
- onDestroy(fn: () => void) {
- this.destroy = fn;
- },
- // TODO: these functions do not match the `onXYZ` and `xyz` pattern elsewhere.
- onEmbeddableDestroyed() {},
- onEmbeddableInputChange() {},
- onResize(fn: (size: { height: number; width: number }) => void) {
- this.resize = fn;
- },
- reload() {},
- resize(_size: { height: number; width: number }) {},
- setFilter() {},
- update() {},
-});
-
-export const assignHandlers = (handlers: Partial = {}): RendererHandlers =>
- Object.assign(createHandlers(), handlers);
-
-// TODO: this is a legacy approach we should unravel in the near future.
-export const createDispatchedHandlerFactory = (
- dispatch: (action: any) => void
-): ((element: CanvasElement) => RendererHandlers) => {
- let isComplete = false;
- let oldElement: CanvasElement | undefined;
- let completeFn = () => {};
-
- return (element: CanvasElement) => {
- // reset isComplete when element changes
- if (!isEqual(oldElement, element)) {
- isComplete = false;
- oldElement = element;
- }
-
- return assignHandlers({
- setFilter(text: string) {
- dispatch(setFilter(text, element.id, true));
- },
-
- getFilter() {
- return element.filter;
- },
-
- onComplete(fn: () => void) {
- completeFn = fn;
- },
-
- getElementId: () => element.id,
-
- onEmbeddableInputChange(embeddableExpression: string) {
- dispatch(updateEmbeddableExpression({ elementId: element.id, embeddableExpression }));
- },
-
- onEmbeddableDestroyed() {
- dispatch(fetchEmbeddableRenderable(element.id));
- },
-
- done() {
- // don't emit if the element is already done
- if (isComplete) {
- return;
- }
-
- isComplete = true;
- completeFn();
- },
- });
- };
-};
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
index 6bcc0db92f1cc..5741f5f2d698c 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
+++ b/x-pack/plugins/canvas/shareable_runtime/components/rendered_element.tsx
@@ -7,13 +7,13 @@
import React, { FC, PureComponent } from 'react';
// @ts-expect-error untyped library
import Style from 'style-it';
+// @ts-expect-error untyped local
import { Positionable } from '../../public/components/positionable/positionable';
// @ts-expect-error untyped local
import { elementToShape } from '../../public/components/workpad_page/utils';
import { CanvasRenderedElement } from '../types';
import { CanvasShareableContext, useCanvasShareableState } from '../context';
import { RendererSpec } from '../../types';
-import { createHandlers } from '../../public/lib/create_handlers';
import css from './rendered_element.module.scss';
@@ -62,7 +62,17 @@ export class RenderedElementComponent extends PureComponent {
}
try {
- fn.render(this.ref.current, value.value, createHandlers());
+ // TODO: These are stubbed, but may need implementation.
+ fn.render(this.ref.current, value.value, {
+ done: () => {},
+ onDestroy: () => {},
+ onResize: () => {},
+ getElementId: () => '',
+ setFilter: () => {},
+ getFilter: () => '',
+ onEmbeddableInputChange: () => {},
+ onEmbeddableDestroyed: () => {},
+ });
} catch (e) {
// eslint-disable-next-line no-console
console.log(as, e.message);
diff --git a/x-pack/plugins/canvas/types/renderers.ts b/x-pack/plugins/canvas/types/renderers.ts
index 772a16aa94c60..2564b045d1cf7 100644
--- a/x-pack/plugins/canvas/types/renderers.ts
+++ b/x-pack/plugins/canvas/types/renderers.ts
@@ -4,29 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { IInterpreterRenderHandlers } from 'src/plugins/expressions';
+type GenericCallback = (callback: () => void) => void;
-type GenericRendererCallback = (callback: () => void) => void;
-
-export interface RendererHandlers extends IInterpreterRenderHandlers {
- /** Handler to invoke when an element should be destroyed. */
- destroy: () => void;
+export interface RendererHandlers {
+ /** Handler to invoke when an element has finished rendering */
+ done: () => void;
/** Get the id of the element being rendered. Can be used as a unique ID in a render function */
getElementId: () => string;
+ /** Handler to invoke when an element is deleted or changes to a different render type */
+ onDestroy: GenericCallback;
+ /** Handler to invoke when an element's dimensions have changed*/
+ onResize: GenericCallback;
/** Retrieves the value of the filter property on the element object persisted on the workpad */
getFilter: () => string;
- /** Handler to invoke when a renderer is considered complete */
- onComplete: (fn: () => void) => void;
- /** Handler to invoke when a rendered embeddable is destroyed */
- onEmbeddableDestroyed: () => void;
- /** Handler to invoke when the input to a function has changed internally */
- onEmbeddableInputChange: (expression: string) => void;
- /** Handler to invoke when an element's dimensions have changed*/
- onResize: GenericRendererCallback;
- /** Handler to invoke when an element should be resized. */
- resize: (size: { height: number; width: number }) => void;
/** Sets the value of the filter property on the element object persisted on the workpad */
setFilter: (filter: string) => void;
+ /** Handler to invoke when the input to a function has changed internally */
+ onEmbeddableInputChange: (expression: string) => void;
+ /** Handler to invoke when a rendered embeddable is destroyed */
+ onEmbeddableDestroyed: () => void;
}
export interface RendererSpec {
From bdd66be0ec99fb5da2949a31c585cdb8bd820e32 Mon Sep 17 00:00:00 2001
From: Tyler Smalley
Date: Wed, 24 Jun 2020 16:21:46 -0700
Subject: [PATCH 16/82] [SECURITY] Disables Cypress tests
Signed-off-by: Tyler Smalley
---
test/scripts/jenkins_security_solution_cypress.sh | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/test/scripts/jenkins_security_solution_cypress.sh b/test/scripts/jenkins_security_solution_cypress.sh
index 23b83cf946d49..8aa3425be0beb 100644
--- a/test/scripts/jenkins_security_solution_cypress.sh
+++ b/test/scripts/jenkins_security_solution_cypress.sh
@@ -11,11 +11,16 @@ export KIBANA_INSTALL_DIR="$destDir"
echo " -> Running security solution cypress tests"
cd "$XPACK_DIR"
-checks-reporter-with-killswitch "Security solution Cypress Tests" \
- node scripts/functional_tests \
- --debug --bail \
- --kibana-install-dir "$KIBANA_INSTALL_DIR" \
- --config test/security_solution_cypress/config.ts
+# Failures across multiple suites, skipping all
+# https://github.com/elastic/kibana/issues/69847
+# https://github.com/elastic/kibana/issues/69848
+# https://github.com/elastic/kibana/issues/69849
+
+# checks-reporter-with-killswitch "Security solution Cypress Tests" \
+# node scripts/functional_tests \
+# --debug --bail \
+# --kibana-install-dir "$KIBANA_INSTALL_DIR" \
+# --config test/security_solution_cypress/config.ts
echo ""
echo ""
From 7d47e96837f80a922f7514e663734c46dde0e6db Mon Sep 17 00:00:00 2001
From: Clint Andrew Hall
Date: Wed, 24 Jun 2020 19:23:20 -0400
Subject: [PATCH 17/82] [7.x] [pre-req] Convert Palettes and Components
(#69065) (#69819)
Co-authored-by: Elastic Machine
---
x-pack/plugins/canvas/.storybook/config.js | 3 +
.../canvas/.storybook/webpack.config.js | 1 +
.../functions/common/palette.test.js | 10 +-
.../functions/common/palette.ts | 5 +-
.../__snapshots__/palette.stories.storyshot | 86 ++++++
.../__examples__/palette.stories.tsx | 32 +++
.../canvas_plugin_src/uis/arguments/index.ts | 1 -
.../uis/arguments/{palette.js => palette.tsx} | 62 +++--
x-pack/plugins/canvas/common/lib/index.ts | 1 -
x-pack/plugins/canvas/common/lib/palettes.js | 152 ----------
x-pack/plugins/canvas/common/lib/palettes.ts | 263 ++++++++++++++++++
x-pack/plugins/canvas/i18n/components.ts | 10 +
x-pack/plugins/canvas/i18n/constants.ts | 1 +
x-pack/plugins/canvas/i18n/index.ts | 1 +
x-pack/plugins/canvas/i18n/lib.ts | 92 ++++++
x-pack/plugins/canvas/i18n/ui.ts | 6 +-
.../palette_picker.stories.storyshot | 237 ++++++++++++++++
.../__examples__/palette_picker.stories.tsx | 25 ++
.../public/components/palette_picker/index.js | 11 -
.../index.js => palette_picker/index.ts} | 6 +-
.../palette_picker/palette_picker.js | 60 ----
.../palette_picker/palette_picker.scss | 42 ---
.../palette_picker/palette_picker.tsx | 92 ++++++
.../palette_swatch/palette_swatch.js | 46 ---
.../palette_swatch/palette_swatch.scss | 35 ---
x-pack/plugins/canvas/public/style/index.scss | 2 -
26 files changed, 898 insertions(+), 384 deletions(-)
create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__examples__/__snapshots__/palette.stories.storyshot
create mode 100644 x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__examples__/palette.stories.tsx
rename x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/{palette.js => palette.tsx} (58%)
delete mode 100644 x-pack/plugins/canvas/common/lib/palettes.js
create mode 100644 x-pack/plugins/canvas/common/lib/palettes.ts
create mode 100644 x-pack/plugins/canvas/i18n/lib.ts
create mode 100644 x-pack/plugins/canvas/public/components/palette_picker/__examples__/__snapshots__/palette_picker.stories.storyshot
create mode 100644 x-pack/plugins/canvas/public/components/palette_picker/__examples__/palette_picker.stories.tsx
delete mode 100644 x-pack/plugins/canvas/public/components/palette_picker/index.js
rename x-pack/plugins/canvas/public/components/{palette_swatch/index.js => palette_picker/index.ts} (62%)
delete mode 100644 x-pack/plugins/canvas/public/components/palette_picker/palette_picker.js
delete mode 100644 x-pack/plugins/canvas/public/components/palette_picker/palette_picker.scss
create mode 100644 x-pack/plugins/canvas/public/components/palette_picker/palette_picker.tsx
delete mode 100644 x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.js
delete mode 100644 x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.scss
diff --git a/x-pack/plugins/canvas/.storybook/config.js b/x-pack/plugins/canvas/.storybook/config.js
index c808a672711ab..04b4e2a8e7b4b 100644
--- a/x-pack/plugins/canvas/.storybook/config.js
+++ b/x-pack/plugins/canvas/.storybook/config.js
@@ -59,6 +59,9 @@ function loadStories() {
// Find all files ending in *.examples.ts
const req = require.context('./..', true, /.(stories|examples).tsx$/);
req.keys().forEach(filename => req(filename));
+
+ // Import Canvas CSS
+ require('../public/style/index.scss')
}
// Set up the Storybook environment with custom settings.
diff --git a/x-pack/plugins/canvas/.storybook/webpack.config.js b/x-pack/plugins/canvas/.storybook/webpack.config.js
index 4d83a3d4fa70f..45a5303d8b0db 100644
--- a/x-pack/plugins/canvas/.storybook/webpack.config.js
+++ b/x-pack/plugins/canvas/.storybook/webpack.config.js
@@ -199,6 +199,7 @@ module.exports = async ({ config }) => {
config.resolve.alias['ui/url/absolute_to_parsed_url'] = path.resolve(__dirname, '../tasks/mocks/uiAbsoluteToParsedUrl');
config.resolve.alias['ui/chrome'] = path.resolve(__dirname, '../tasks/mocks/uiChrome');
config.resolve.alias.ui = path.resolve(KIBANA_ROOT, 'src/legacy/ui/public');
+ config.resolve.alias['src/legacy/ui/public/styles/styling_constants'] = path.resolve(KIBANA_ROOT, 'src/legacy/ui/public/styles/_styling_constants.scss');
config.resolve.alias.ng_mock$ = path.resolve(KIBANA_ROOT, 'src/test_utils/public/ng_mock');
return config;
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.test.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.test.js
index af03297ad666b..01cabd171c2fe 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.test.js
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.test.js
@@ -5,7 +5,7 @@
*/
import { functionWrapper } from '../../../__tests__/helpers/function_wrapper';
-import { palettes } from '../../../common/lib/palettes';
+import { paulTor14 } from '../../../common/lib/palettes';
import { palette } from './palette';
describe('palette', () => {
@@ -25,7 +25,7 @@ describe('palette', () => {
it('defaults to pault_tor_14 colors', () => {
const result = fn(null);
- expect(result.colors).toEqual(palettes.paul_tor_14.colors);
+ expect(result.colors).toEqual(paulTor14.colors);
});
});
@@ -47,17 +47,17 @@ describe('palette', () => {
describe('reverse', () => {
it('reverses order of the colors', () => {
const result = fn(null, { reverse: true });
- expect(result.colors).toEqual(palettes.paul_tor_14.colors.reverse());
+ expect(result.colors).toEqual(paulTor14.colors.reverse());
});
it('keeps the original order of the colors', () => {
const result = fn(null, { reverse: false });
- expect(result.colors).toEqual(palettes.paul_tor_14.colors);
+ expect(result.colors).toEqual(paulTor14.colors);
});
it(`defaults to 'false`, () => {
const result = fn(null);
- expect(result.colors).toEqual(palettes.paul_tor_14.colors);
+ expect(result.colors).toEqual(paulTor14.colors);
});
});
});
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts
index f27abe261e2e2..50d62a19b2361 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/palette.ts
@@ -5,8 +5,7 @@
*/
import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common';
-// @ts-expect-error untyped local
-import { palettes } from '../../../common/lib/palettes';
+import { paulTor14 } from '../../../common/lib/palettes';
import { getFunctionHelp } from '../../../i18n';
interface Arguments {
@@ -52,7 +51,7 @@ export function palette(): ExpressionFunctionDefinition<'palette', null, Argumen
},
fn: (input, args) => {
const { color, reverse, gradient } = args;
- const colors = ([] as string[]).concat(color || palettes.paul_tor_14.colors);
+ const colors = ([] as string[]).concat(color || paulTor14.colors);
return {
type: 'palette',
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__examples__/__snapshots__/palette.stories.storyshot b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__examples__/__snapshots__/palette.stories.storyshot
new file mode 100644
index 0000000000000..385b16d3d8e8e
--- /dev/null
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__examples__/__snapshots__/palette.stories.storyshot
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots arguments/Palette default 1`] = `
+
+
+
+
+
+
+
+ Select an option:
+
+ , is selected
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__examples__/palette.stories.tsx b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__examples__/palette.stories.tsx
new file mode 100644
index 0000000000000..6bc285a3d66d2
--- /dev/null
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/__examples__/palette.stories.tsx
@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { storiesOf } from '@storybook/react';
+import React from 'react';
+import { action } from '@storybook/addon-actions';
+import { PaletteArgInput } from '../palette';
+import { paulTor14 } from '../../../../common/lib/palettes';
+
+storiesOf('arguments/Palette', module).add('default', () => (
+
+));
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts
index 94a9cf28aef69..ddf428d884917 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/index.ts
@@ -15,7 +15,6 @@ import { imageUpload } from './image_upload';
// @ts-expect-error untyped local
import { number } from './number';
import { numberFormatInitializer } from './number_format';
-// @ts-expect-error untyped local
import { palette } from './palette';
// @ts-expect-error untyped local
import { percentage } from './percentage';
diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx
similarity index 58%
rename from x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js
rename to x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx
index eddaa20a4800e..a33d000a1f656 100644
--- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js
+++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.tsx
@@ -4,45 +4,63 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { FC } from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { getType } from '@kbn/interpreter/common';
+import { ExpressionAstFunction, ExpressionAstExpression } from 'src/plugins/expressions';
import { PalettePicker } from '../../../public/components/palette_picker';
import { templateFromReactComponent } from '../../../public/lib/template_from_react_component';
import { ArgumentStrings } from '../../../i18n';
+import { identifyPalette, ColorPalette } from '../../../common/lib';
const { Palette: strings } = ArgumentStrings;
-const PaletteArgInput = ({ onValueChange, argValue, renderError }) => {
- // Why is this neccesary? Does the dialog really need to know what parameter it is setting?
-
- const throwNotParsed = () => renderError();
+interface Props {
+ onValueChange: (value: ExpressionAstExpression) => void;
+ argValue: ExpressionAstExpression;
+ renderError: () => void;
+ argId?: string;
+}
+export const PaletteArgInput: FC = ({ onValueChange, argId, argValue, renderError }) => {
// TODO: This is weird, its basically a reimplementation of what the interpretter would return.
- // Probably a better way todo this, and maybe a better way to handle template stype objects in general?
- function astToPalette({ chain }) {
+ // Probably a better way todo this, and maybe a better way to handle template type objects in general?
+ const astToPalette = ({ chain }: { chain: ExpressionAstFunction[] }): ColorPalette | null => {
if (chain.length !== 1 || chain[0].function !== 'palette') {
- throwNotParsed();
+ renderError();
+ return null;
}
+
try {
const colors = chain[0].arguments._.map((astObj) => {
if (getType(astObj) !== 'string') {
- throwNotParsed();
+ renderError();
}
return astObj;
- });
+ }) as string[];
- const gradient = get(chain[0].arguments.gradient, '[0]');
+ const gradient = get(chain[0].arguments.gradient, '[0]');
+ const palette = identifyPalette({ colors, gradient });
- return { colors, gradient };
+ if (palette) {
+ return palette;
+ }
+
+ return ({
+ id: 'custom',
+ label: strings.getCustomPaletteLabel(),
+ colors,
+ gradient,
+ } as any) as ColorPalette;
} catch (e) {
- throwNotParsed();
+ renderError();
}
- }
+ return null;
+ };
- function handleChange(palette) {
- const astObj = {
+ const handleChange = (palette: ColorPalette): void => {
+ const astObj: ExpressionAstExpression = {
type: 'expression',
chain: [
{
@@ -57,16 +75,20 @@ const PaletteArgInput = ({ onValueChange, argValue, renderError }) => {
};
onValueChange(astObj);
- }
+ };
const palette = astToPalette(argValue);
- return (
-
- );
+ if (!palette) {
+ renderError();
+ return null;
+ }
+
+ return ;
};
PaletteArgInput.propTypes = {
+ argId: PropTypes.string,
onValueChange: PropTypes.func.isRequired,
argValue: PropTypes.any.isRequired,
renderError: PropTypes.func,
diff --git a/x-pack/plugins/canvas/common/lib/index.ts b/x-pack/plugins/canvas/common/lib/index.ts
index 4cb3cbbb9b4e6..6bd7e0bc9948f 100644
--- a/x-pack/plugins/canvas/common/lib/index.ts
+++ b/x-pack/plugins/canvas/common/lib/index.ts
@@ -26,7 +26,6 @@ export * from './hex_to_rgb';
export * from './httpurl';
// @ts-expect-error missing local definition
export * from './missing_asset';
-// @ts-expect-error missing local definition
export * from './palettes';
export * from './pivot_object_array';
// @ts-expect-error missing local definition
diff --git a/x-pack/plugins/canvas/common/lib/palettes.js b/x-pack/plugins/canvas/common/lib/palettes.js
deleted file mode 100644
index 3fe977ec3862c..0000000000000
--- a/x-pack/plugins/canvas/common/lib/palettes.js
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-/*
- This should be pluggable
-*/
-
-export const palettes = {
- paul_tor_14: {
- colors: [
- '#882E72',
- '#B178A6',
- '#D6C1DE',
- '#1965B0',
- '#5289C7',
- '#7BAFDE',
- '#4EB265',
- '#90C987',
- '#CAE0AB',
- '#F7EE55',
- '#F6C141',
- '#F1932D',
- '#E8601C',
- '#DC050C',
- ],
- gradient: false,
- },
- paul_tor_21: {
- colors: [
- '#771155',
- '#AA4488',
- '#CC99BB',
- '#114477',
- '#4477AA',
- '#77AADD',
- '#117777',
- '#44AAAA',
- '#77CCCC',
- '#117744',
- '#44AA77',
- '#88CCAA',
- '#777711',
- '#AAAA44',
- '#DDDD77',
- '#774411',
- '#AA7744',
- '#DDAA77',
- '#771122',
- '#AA4455',
- '#DD7788',
- ],
- gradient: false,
- },
- earth_tones: {
- colors: [
- '#842113',
- '#984d23',
- '#32221c',
- '#739379',
- '#dab150',
- '#4d2521',
- '#716c49',
- '#bb3918',
- '#7e5436',
- '#c27c34',
- '#72392e',
- '#8f8b7e',
- ],
- gradient: false,
- },
- canvas: {
- colors: [
- '#01A4A4',
- '#CC6666',
- '#D0D102',
- '#616161',
- '#00A1CB',
- '#32742C',
- '#F18D05',
- '#113F8C',
- '#61AE24',
- '#D70060',
- ],
- gradient: false,
- },
- color_blind: {
- colors: [
- '#1ea593',
- '#2b70f7',
- '#ce0060',
- '#38007e',
- '#fca5d3',
- '#f37020',
- '#e49e29',
- '#b0916f',
- '#7b000b',
- '#34130c',
- ],
- gradient: false,
- },
- elastic_teal: {
- colors: ['#C5FAF4', '#0F6259'],
- gradient: true,
- },
- elastic_blue: {
- colors: ['#7ECAE3', '#003A4D'],
- gradient: true,
- },
- elastic_yellow: {
- colors: ['#FFE674', '#4D3F00'],
- gradient: true,
- },
- elastic_pink: {
- colors: ['#FEA8D5', '#531E3A'],
- gradient: true,
- },
- elastic_green: {
- colors: ['#D3FB71', '#131A00'],
- gradient: true,
- },
- elastic_orange: {
- colors: ['#FFC68A', '#7B3F00'],
- gradient: true,
- },
- elastic_purple: {
- colors: ['#CCC7DF', '#130351'],
- gradient: true,
- },
- green_blue_red: {
- colors: ['#D3FB71', '#7ECAE3', '#f03b20'],
- gradient: true,
- },
- yellow_green: {
- colors: ['#f7fcb9', '#addd8e', '#31a354'],
- gradient: true,
- },
- yellow_blue: {
- colors: ['#edf8b1', '#7fcdbb', '#2c7fb8'],
- gradient: true,
- },
- yellow_red: {
- colors: ['#ffeda0', '#feb24c', '#f03b20'],
- gradient: true,
- },
- instagram: {
- colors: ['#833ab4', '#fd1d1d', '#fcb045'],
- gradient: true,
- },
-};
diff --git a/x-pack/plugins/canvas/common/lib/palettes.ts b/x-pack/plugins/canvas/common/lib/palettes.ts
new file mode 100644
index 0000000000000..1469ba63967c0
--- /dev/null
+++ b/x-pack/plugins/canvas/common/lib/palettes.ts
@@ -0,0 +1,263 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { isEqual } from 'lodash';
+import { LibStrings } from '../../i18n';
+
+const { Palettes: strings } = LibStrings;
+
+/**
+ * This type contains a unions of all supported palette ids.
+ */
+export type PaletteID = typeof palettes[number]['id'];
+
+/**
+ * An interface representing a color palette in Canvas, with a textual label and a set of
+ * hex values.
+ */
+export interface ColorPalette {
+ id: PaletteID;
+ label: string;
+ colors: string[];
+ gradient: boolean;
+}
+
+// This function allows one to create a strongly-typed palette for inclusion in
+// the palette collection. As a result, the values and labels are known to the
+// type system, preventing one from specifying a non-existent palette at build
+// time.
+function createPalette<
+ RawPalette extends {
+ id: RawPaletteID;
+ },
+ RawPaletteID extends string
+>(palette: RawPalette) {
+ return palette;
+}
+
+/**
+ * Return a palette given a set of colors and gradient. Returns undefined if the
+ * palette doesn't match.
+ */
+export const identifyPalette = (
+ input: Pick
+): ColorPalette | undefined => {
+ return palettes.find((palette) => {
+ const { colors, gradient } = palette;
+ return gradient === input.gradient && isEqual(colors, input.colors);
+ });
+};
+
+export const paulTor14 = createPalette({
+ id: 'paul_tor_14',
+ label: 'Paul Tor 14',
+ colors: [
+ '#882E72',
+ '#B178A6',
+ '#D6C1DE',
+ '#1965B0',
+ '#5289C7',
+ '#7BAFDE',
+ '#4EB265',
+ '#90C987',
+ '#CAE0AB',
+ '#F7EE55',
+ '#F6C141',
+ '#F1932D',
+ '#E8601C',
+ '#DC050C',
+ ],
+ gradient: false,
+});
+
+export const paulTor21 = createPalette({
+ id: 'paul_tor_21',
+ label: 'Paul Tor 21',
+ colors: [
+ '#771155',
+ '#AA4488',
+ '#CC99BB',
+ '#114477',
+ '#4477AA',
+ '#77AADD',
+ '#117777',
+ '#44AAAA',
+ '#77CCCC',
+ '#117744',
+ '#44AA77',
+ '#88CCAA',
+ '#777711',
+ '#AAAA44',
+ '#DDDD77',
+ '#774411',
+ '#AA7744',
+ '#DDAA77',
+ '#771122',
+ '#AA4455',
+ '#DD7788',
+ ],
+ gradient: false,
+});
+
+export const earthTones = createPalette({
+ id: 'earth_tones',
+ label: strings.getEarthTones(),
+ colors: [
+ '#842113',
+ '#984d23',
+ '#32221c',
+ '#739379',
+ '#dab150',
+ '#4d2521',
+ '#716c49',
+ '#bb3918',
+ '#7e5436',
+ '#c27c34',
+ '#72392e',
+ '#8f8b7e',
+ ],
+ gradient: false,
+});
+
+export const canvas = createPalette({
+ id: 'canvas',
+ label: strings.getCanvas(),
+ colors: [
+ '#01A4A4',
+ '#CC6666',
+ '#D0D102',
+ '#616161',
+ '#00A1CB',
+ '#32742C',
+ '#F18D05',
+ '#113F8C',
+ '#61AE24',
+ '#D70060',
+ ],
+ gradient: false,
+});
+
+export const colorBlind = createPalette({
+ id: 'color_blind',
+ label: strings.getColorBlind(),
+ colors: [
+ '#1ea593',
+ '#2b70f7',
+ '#ce0060',
+ '#38007e',
+ '#fca5d3',
+ '#f37020',
+ '#e49e29',
+ '#b0916f',
+ '#7b000b',
+ '#34130c',
+ ],
+ gradient: false,
+});
+
+export const elasticTeal = createPalette({
+ id: 'elastic_teal',
+ label: strings.getElasticTeal(),
+ colors: ['#7ECAE3', '#003A4D'],
+ gradient: true,
+});
+
+export const elasticBlue = createPalette({
+ id: 'elastic_blue',
+ label: strings.getElasticBlue(),
+ colors: ['#C5FAF4', '#0F6259'],
+ gradient: true,
+});
+
+export const elasticYellow = createPalette({
+ id: 'elastic_yellow',
+ label: strings.getElasticYellow(),
+ colors: ['#FFE674', '#4D3F00'],
+ gradient: true,
+});
+
+export const elasticPink = createPalette({
+ id: 'elastic_pink',
+ label: strings.getElasticPink(),
+ colors: ['#FEA8D5', '#531E3A'],
+ gradient: true,
+});
+
+export const elasticGreen = createPalette({
+ id: 'elastic_green',
+ label: strings.getElasticGreen(),
+ colors: ['#D3FB71', '#131A00'],
+ gradient: true,
+});
+
+export const elasticOrange = createPalette({
+ id: 'elastic_orange',
+ label: strings.getElasticOrange(),
+ colors: ['#FFC68A', '#7B3F00'],
+ gradient: true,
+});
+
+export const elasticPurple = createPalette({
+ id: 'elastic_purple',
+ label: strings.getElasticPurple(),
+ colors: ['#CCC7DF', '#130351'],
+ gradient: true,
+});
+
+export const greenBlueRed = createPalette({
+ id: 'green_blue_red',
+ label: strings.getGreenBlueRed(),
+ colors: ['#D3FB71', '#7ECAE3', '#f03b20'],
+ gradient: true,
+});
+
+export const yellowGreen = createPalette({
+ id: 'yellow_green',
+ label: strings.getYellowGreen(),
+ colors: ['#f7fcb9', '#addd8e', '#31a354'],
+ gradient: true,
+});
+
+export const yellowBlue = createPalette({
+ id: 'yellow_blue',
+ label: strings.getYellowBlue(),
+ colors: ['#edf8b1', '#7fcdbb', '#2c7fb8'],
+ gradient: true,
+});
+
+export const yellowRed = createPalette({
+ id: 'yellow_red',
+ label: strings.getYellowRed(),
+ colors: ['#ffeda0', '#feb24c', '#f03b20'],
+ gradient: true,
+});
+
+export const instagram = createPalette({
+ id: 'instagram',
+ label: strings.getInstagram(),
+ colors: ['#833ab4', '#fd1d1d', '#fcb045'],
+ gradient: true,
+});
+
+export const palettes = [
+ paulTor14,
+ paulTor21,
+ earthTones,
+ canvas,
+ colorBlind,
+ elasticTeal,
+ elasticBlue,
+ elasticYellow,
+ elasticPink,
+ elasticGreen,
+ elasticOrange,
+ elasticPurple,
+ greenBlueRed,
+ yellowGreen,
+ yellowBlue,
+ yellowRed,
+ instagram,
+];
diff --git a/x-pack/plugins/canvas/i18n/components.ts b/x-pack/plugins/canvas/i18n/components.ts
index de16bc2101e8c..0b512c80b209b 100644
--- a/x-pack/plugins/canvas/i18n/components.ts
+++ b/x-pack/plugins/canvas/i18n/components.ts
@@ -586,6 +586,16 @@ export const ComponentStrings = {
defaultMessage: 'Delete',
}),
},
+ PalettePicker: {
+ getEmptyPaletteLabel: () =>
+ i18n.translate('xpack.canvas.palettePicker.emptyPaletteLabel', {
+ defaultMessage: 'None',
+ }),
+ getNoPaletteFoundErrorTitle: () =>
+ i18n.translate('xpack.canvas.palettePicker.noPaletteFoundErrorTitle', {
+ defaultMessage: 'Color palette not found',
+ }),
+ },
SavedElementsModal: {
getAddNewElementDescription: () =>
i18n.translate('xpack.canvas.savedElementsModal.addNewElementDescription', {
diff --git a/x-pack/plugins/canvas/i18n/constants.ts b/x-pack/plugins/canvas/i18n/constants.ts
index 099effc697fc5..af82d0afc7e9f 100644
--- a/x-pack/plugins/canvas/i18n/constants.ts
+++ b/x-pack/plugins/canvas/i18n/constants.ts
@@ -20,6 +20,7 @@ export const FONT_FAMILY = '`font-family`';
export const FONT_WEIGHT = '`font-weight`';
export const HEX = 'HEX';
export const HTML = 'HTML';
+export const INSTAGRAM = 'Instagram';
export const ISO8601 = 'ISO8601';
export const JS = 'JavaScript';
export const JSON = 'JSON';
diff --git a/x-pack/plugins/canvas/i18n/index.ts b/x-pack/plugins/canvas/i18n/index.ts
index 864311d34aca0..3bf1fa077130c 100644
--- a/x-pack/plugins/canvas/i18n/index.ts
+++ b/x-pack/plugins/canvas/i18n/index.ts
@@ -11,6 +11,7 @@ export * from './errors';
export * from './expression_types';
export * from './elements';
export * from './functions';
+export * from './lib';
export * from './renderers';
export * from './shortcuts';
export * from './tags';
diff --git a/x-pack/plugins/canvas/i18n/lib.ts b/x-pack/plugins/canvas/i18n/lib.ts
new file mode 100644
index 0000000000000..eca6dc44354a2
--- /dev/null
+++ b/x-pack/plugins/canvas/i18n/lib.ts
@@ -0,0 +1,92 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+import { CANVAS, INSTAGRAM } from './constants';
+
+export const LibStrings = {
+ Palettes: {
+ getEarthTones: () =>
+ i18n.translate('xpack.canvas.lib.palettes.earthTonesLabel', {
+ defaultMessage: 'Earth Tones',
+ }),
+ getCanvas: () =>
+ i18n.translate('xpack.canvas.lib.palettes.canvasLabel', {
+ defaultMessage: '{CANVAS}',
+ values: {
+ CANVAS,
+ },
+ }),
+
+ getColorBlind: () =>
+ i18n.translate('xpack.canvas.lib.palettes.colorBlindLabel', {
+ defaultMessage: 'Color Blind',
+ }),
+
+ getElasticTeal: () =>
+ i18n.translate('xpack.canvas.lib.palettes.elasticTealLabel', {
+ defaultMessage: 'Elastic Teal',
+ }),
+
+ getElasticBlue: () =>
+ i18n.translate('xpack.canvas.lib.palettes.elasticBlueLabel', {
+ defaultMessage: 'Elastic Blue',
+ }),
+
+ getElasticYellow: () =>
+ i18n.translate('xpack.canvas.lib.palettes.elasticYellowLabel', {
+ defaultMessage: 'Elastic Yellow',
+ }),
+
+ getElasticPink: () =>
+ i18n.translate('xpack.canvas.lib.palettes.elasticPinkLabel', {
+ defaultMessage: 'Elastic Pink',
+ }),
+
+ getElasticGreen: () =>
+ i18n.translate('xpack.canvas.lib.palettes.elasticGreenLabel', {
+ defaultMessage: 'Elastic Green',
+ }),
+
+ getElasticOrange: () =>
+ i18n.translate('xpack.canvas.lib.palettes.elasticOrangeLabel', {
+ defaultMessage: 'Elastic Orange',
+ }),
+
+ getElasticPurple: () =>
+ i18n.translate('xpack.canvas.lib.palettes.elasticPurpleLabel', {
+ defaultMessage: 'Elastic Purple',
+ }),
+
+ getGreenBlueRed: () =>
+ i18n.translate('xpack.canvas.lib.palettes.greenBlueRedLabel', {
+ defaultMessage: 'Green, Blue, Red',
+ }),
+
+ getYellowGreen: () =>
+ i18n.translate('xpack.canvas.lib.palettes.yellowGreenLabel', {
+ defaultMessage: 'Yellow, Green',
+ }),
+
+ getYellowBlue: () =>
+ i18n.translate('xpack.canvas.lib.palettes.yellowBlueLabel', {
+ defaultMessage: 'Yellow, Blue',
+ }),
+
+ getYellowRed: () =>
+ i18n.translate('xpack.canvas.lib.palettes.yellowRedLabel', {
+ defaultMessage: 'Yellow, Red',
+ }),
+
+ getInstagram: () =>
+ i18n.translate('xpack.canvas.lib.palettes.instagramLabel', {
+ defaultMessage: '{INSTAGRAM}',
+ values: {
+ INSTAGRAM,
+ },
+ }),
+ },
+};
diff --git a/x-pack/plugins/canvas/i18n/ui.ts b/x-pack/plugins/canvas/i18n/ui.ts
index f69f9e747ab90..bc282db203be2 100644
--- a/x-pack/plugins/canvas/i18n/ui.ts
+++ b/x-pack/plugins/canvas/i18n/ui.ts
@@ -232,7 +232,11 @@ export const ArgumentStrings = {
}),
getHelp: () =>
i18n.translate('xpack.canvas.uis.arguments.paletteLabel', {
- defaultMessage: 'Choose a color palette',
+ defaultMessage: 'The collection of colors used to render the element',
+ }),
+ getCustomPaletteLabel: () =>
+ i18n.translate('xpack.canvas.uis.arguments.customPaletteLabel', {
+ defaultMessage: 'Custom',
}),
},
Percentage: {
diff --git a/x-pack/plugins/canvas/public/components/palette_picker/__examples__/__snapshots__/palette_picker.stories.storyshot b/x-pack/plugins/canvas/public/components/palette_picker/__examples__/__snapshots__/palette_picker.stories.storyshot
new file mode 100644
index 0000000000000..d3809b4c3979f
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/palette_picker/__examples__/__snapshots__/palette_picker.stories.storyshot
@@ -0,0 +1,237 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Storyshots components/Color/PalettePicker clearable 1`] = `
+
+
+
+
+
+
+
+ Select an option: None, is selected
+
+
+ None
+
+
+
+
+
+
+
+`;
+
+exports[`Storyshots components/Color/PalettePicker default 1`] = `
+
+
+
+
+
+
+
+ Select an option:
+
+ , is selected
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`Storyshots components/Color/PalettePicker interactive 1`] = `
+
+
+
+
+
+
+
+ Select an option:
+
+ , is selected
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/x-pack/plugins/canvas/public/components/palette_picker/__examples__/palette_picker.stories.tsx b/x-pack/plugins/canvas/public/components/palette_picker/__examples__/palette_picker.stories.tsx
new file mode 100644
index 0000000000000..b1ae860e80efb
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/palette_picker/__examples__/palette_picker.stories.tsx
@@ -0,0 +1,25 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC, useState } from 'react';
+import { action } from '@storybook/addon-actions';
+import { storiesOf } from '@storybook/react';
+import { PalettePicker } from '../palette_picker';
+
+import { paulTor14, ColorPalette } from '../../../../common/lib/palettes';
+
+const Interactive: FC = () => {
+ const [palette, setPalette] = useState(paulTor14);
+ return ;
+};
+
+storiesOf('components/Color/PalettePicker', module)
+ .addDecorator((fn) => {fn()}
)
+ .add('default', () => )
+ .add('clearable', () => (
+
+ ))
+ .add('interactive', () => );
diff --git a/x-pack/plugins/canvas/public/components/palette_picker/index.js b/x-pack/plugins/canvas/public/components/palette_picker/index.js
deleted file mode 100644
index 33d1d22777183..0000000000000
--- a/x-pack/plugins/canvas/public/components/palette_picker/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { pure } from 'recompose';
-
-import { PalettePicker as Component } from './palette_picker';
-
-export const PalettePicker = pure(Component);
diff --git a/x-pack/plugins/canvas/public/components/palette_swatch/index.js b/x-pack/plugins/canvas/public/components/palette_picker/index.ts
similarity index 62%
rename from x-pack/plugins/canvas/public/components/palette_swatch/index.js
rename to x-pack/plugins/canvas/public/components/palette_picker/index.ts
index 2be37a8338b2b..840600698c5a4 100644
--- a/x-pack/plugins/canvas/public/components/palette_swatch/index.js
+++ b/x-pack/plugins/canvas/public/components/palette_picker/index.ts
@@ -4,8 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { pure } from 'recompose';
-
-import { PaletteSwatch as Component } from './palette_swatch';
-
-export const PaletteSwatch = pure(Component);
+export { PalettePicker } from './palette_picker';
diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.js b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.js
deleted file mode 100644
index ca2a499feb84c..0000000000000
--- a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
-import { map } from 'lodash';
-import { Popover } from '../popover';
-import { PaletteSwatch } from '../palette_swatch';
-import { palettes } from '../../../common/lib/palettes';
-
-export const PalettePicker = ({ onChange, value, anchorPosition, ariaLabel }) => {
- const button = (handleClick) => (
-
-
-
- );
-
- return (
-
- {() => (
-
- {map(palettes, (palette, name) => (
-
onChange(palette)}
- className="canvasPalettePicker__swatch"
- style={{ width: '100%' }}
- >
-
-
- {name.replace(/_/g, ' ')}
-
-
-
-
-
-
- ))}
-
- )}
-
- );
-};
-
-PalettePicker.propTypes = {
- value: PropTypes.object,
- onChange: PropTypes.func,
- anchorPosition: PropTypes.string,
-};
diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.scss b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.scss
deleted file mode 100644
index f837d47682f61..0000000000000
--- a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-.canvasPalettePicker {
- display: inline-block;
- width: 100%;
-}
-
-.canvasPalettePicker__swatches {
- @include euiScrollBar;
-
- width: 280px;
- height: 250px;
- overflow-y: scroll;
-}
-
-.canvasPalettePicker__swatchesPanel {
- padding: $euiSizeS 0 !important; // sass-lint:disable-line no-important
-}
-
-.canvasPalettePicker__swatch {
- padding: $euiSizeS $euiSize;
-
- &:hover,
- &:focus {
- text-decoration: underline;
- background-color: $euiColorLightestShade;
-
- .canvasPaletteSwatch,
- .canvasPaletteSwatch__background {
- transform: scaleY(2);
- }
-
- .canvasPalettePicker__label {
- color: $euiTextColor;
- }
- }
-}
-
-.canvasPalettePicker__label {
- font-size: $euiFontSizeXS;
- text-transform: capitalize;
- text-align: left;
- color: $euiColorDarkShade;
-}
diff --git a/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.tsx b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.tsx
new file mode 100644
index 0000000000000..dec09a5335d95
--- /dev/null
+++ b/x-pack/plugins/canvas/public/components/palette_picker/palette_picker.tsx
@@ -0,0 +1,92 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import PropTypes from 'prop-types';
+import { EuiColorPalettePicker, EuiColorPalettePickerPaletteProps } from '@elastic/eui';
+import { palettes, ColorPalette } from '../../../common/lib/palettes';
+import { ComponentStrings } from '../../../i18n';
+
+const { PalettePicker: strings } = ComponentStrings;
+
+interface RequiredProps {
+ id?: string;
+ onChange?: (palette: ColorPalette) => void;
+ palette: ColorPalette;
+ clearable?: false;
+}
+
+interface ClearableProps {
+ id?: string;
+ onChange?: (palette: ColorPalette | null) => void;
+ palette: ColorPalette | null;
+ clearable: true;
+}
+
+type Props = RequiredProps | ClearableProps;
+
+export const PalettePicker: FC = (props) => {
+ const colorPalettes: EuiColorPalettePickerPaletteProps[] = palettes.map((item) => ({
+ value: item.id,
+ title: item.label,
+ type: item.gradient ? 'gradient' : 'fixed',
+ palette: item.colors,
+ }));
+
+ if (props.clearable) {
+ const { palette, onChange = () => {} } = props;
+
+ colorPalettes.unshift({
+ value: 'clear',
+ title: strings.getEmptyPaletteLabel(),
+ type: 'text',
+ });
+
+ const onPickerChange = (value: string) => {
+ const canvasPalette = palettes.find((item) => item.id === value);
+ onChange(canvasPalette || null);
+ };
+
+ return (
+
+ );
+ }
+
+ const { palette, onChange = () => {} } = props;
+
+ const onPickerChange = (value: string) => {
+ const canvasPalette = palettes.find((item) => item.id === value);
+
+ if (!canvasPalette) {
+ throw new Error(strings.getNoPaletteFoundErrorTitle());
+ }
+
+ onChange(canvasPalette);
+ };
+
+ return (
+
+ );
+};
+
+PalettePicker.propTypes = {
+ id: PropTypes.string,
+ palette: PropTypes.object,
+ onChange: PropTypes.func,
+ clearable: PropTypes.bool,
+};
diff --git a/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.js b/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.js
deleted file mode 100644
index 71d16260e00c7..0000000000000
--- a/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-export const PaletteSwatch = ({ colors, gradient }) => {
- let colorBoxes;
-
- if (!gradient) {
- colorBoxes = colors.map((color) => (
-
- ));
- } else {
- colorBoxes = [
-
,
- ];
- }
-
- return (
-
- );
-};
-
-PaletteSwatch.propTypes = {
- colors: PropTypes.array,
- gradient: PropTypes.bool,
-};
diff --git a/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.scss b/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.scss
deleted file mode 100644
index b57c520a5b07f..0000000000000
--- a/x-pack/plugins/canvas/public/components/palette_swatch/palette_swatch.scss
+++ /dev/null
@@ -1,35 +0,0 @@
-.canvasPaletteSwatch {
- display: inline-block;
- position: relative;
- height: $euiSizeXS;
- width: 100%;
- overflow: hidden;
- text-align: left;
- transform: scaleY(1);
- transition: transform $euiAnimSlightResistance $euiAnimSpeedExtraFast;
-
- .canvasPaletteSwatch__background {
- position: absolute;
- height: $euiSizeXS;
- top: 0;
- left: 0;
- width: 100%;
- transform: scaleY(1);
- transition: transform $euiAnimSlightResistance $euiAnimSpeedExtraFast;
- }
-
- .canvasPaletteSwatch__foreground {
- position: absolute;
- height: 100%; // TODO: No idea why this can't be 25, but it leaves a 1px white spot in the palettePicker if its 25
- top: 0;
- left: 0;
- white-space: nowrap;
- width: 100%;
- display: flex;
- }
-
- .canvasPaletteSwatch__box {
- display: inline-block;
- width: 100%;
- }
-}
diff --git a/x-pack/plugins/canvas/public/style/index.scss b/x-pack/plugins/canvas/public/style/index.scss
index 7b4e1271cca1d..78a34a58f5f78 100644
--- a/x-pack/plugins/canvas/public/style/index.scss
+++ b/x-pack/plugins/canvas/public/style/index.scss
@@ -39,8 +39,6 @@
@import '../components/loading/loading';
@import '../components/navbar/navbar';
@import '../components/page_manager/page_manager';
-@import '../components/palette_picker/palette_picker';
-@import '../components/palette_swatch/palette_swatch';
@import '../components/positionable/positionable';
@import '../components/rotation_handle/rotation_handle';
@import '../components/shape_preview/shape_preview';
From 3d821af4514f06449b66dc38b5ae8279381d8002 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Wed, 24 Jun 2020 19:32:01 -0400
Subject: [PATCH 18/82] [Ingest Manager] Support registration of server side
callbacks for Create Datasource API (#69428) (#69840)
* Ingest: Expose `registerExternalCallback()` method out of Ingest server `start` lifecycle
* Ingest: Add support for External Callbacks on REST `createDatasourceHandler()`
* Ingest: expose DatasourceServices to Plugin start interface
* Endpoint: Added Endpoint Ingest handler for Create Datasources
- Also moved the temporary logic from the middleware
to the handler (still temporary)
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
x-pack/plugins/ingest_manager/server/index.ts | 3 +
x-pack/plugins/ingest_manager/server/mocks.ts | 36 ++
.../plugins/ingest_manager/server/plugin.ts | 26 +-
.../datasource/datasource_handlers.test.ts | 332 ++++++++++++++++++
.../server/routes/datasource/handlers.ts | 41 ++-
.../server/services/app_context.ts | 20 +-
.../server/services/datasource.ts | 1 +
.../policy/store/policy_details/middleware.ts | 18 -
.../endpoint/alerts/handlers/alerts.test.ts | 6 +-
.../endpoint/endpoint_app_context_services.ts | 13 +-
.../server/endpoint/ingest_integration.ts | 49 +++
.../server/endpoint/mocks.ts | 25 +-
.../endpoint/routes/metadata/metadata.test.ts | 17 +-
.../endpoint/routes/policy/handlers.test.ts | 12 +-
.../security_solution/server/plugin.ts | 2 +
15 files changed, 553 insertions(+), 48 deletions(-)
create mode 100644 x-pack/plugins/ingest_manager/server/mocks.ts
create mode 100644 x-pack/plugins/ingest_manager/server/routes/datasource/datasource_handlers.test.ts
create mode 100644 x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts
diff --git a/x-pack/plugins/ingest_manager/server/index.ts b/x-pack/plugins/ingest_manager/server/index.ts
index f6b2d7ccc6d48..1e9011c9dfe4f 100644
--- a/x-pack/plugins/ingest_manager/server/index.ts
+++ b/x-pack/plugins/ingest_manager/server/index.ts
@@ -11,6 +11,7 @@ export {
IngestManagerSetupContract,
IngestManagerSetupDeps,
IngestManagerStartContract,
+ ExternalCallback,
} from './plugin';
export const config = {
@@ -42,6 +43,8 @@ export const config = {
export type IngestManagerConfigType = TypeOf;
+export { DatasourceServiceInterface } from './services/datasource';
+
export const plugin = (initializerContext: PluginInitializerContext) => {
return new IngestManagerPlugin(initializerContext);
};
diff --git a/x-pack/plugins/ingest_manager/server/mocks.ts b/x-pack/plugins/ingest_manager/server/mocks.ts
new file mode 100644
index 0000000000000..3bdef14dc85a0
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/mocks.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
+import { IngestManagerAppContext } from './plugin';
+import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
+import { securityMock } from '../../security/server/mocks';
+import { DatasourceServiceInterface } from './services/datasource';
+
+export const createAppContextStartContractMock = (): IngestManagerAppContext => {
+ return {
+ encryptedSavedObjectsStart: encryptedSavedObjectsMock.createStart(),
+ savedObjects: savedObjectsServiceMock.createStartContract(),
+ security: securityMock.createSetup(),
+ logger: loggingSystemMock.create().get(),
+ isProductionMode: true,
+ kibanaVersion: '8.0.0',
+ };
+};
+
+export const createDatasourceServiceMock = () => {
+ return {
+ assignPackageStream: jest.fn(),
+ buildDatasourceFromPackage: jest.fn(),
+ bulkCreate: jest.fn(),
+ create: jest.fn(),
+ delete: jest.fn(),
+ get: jest.fn(),
+ getByIDs: jest.fn(),
+ list: jest.fn(),
+ update: jest.fn(),
+ } as jest.Mocked;
+};
diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts
index ca13304dcc0dc..6cb448bbfa959 100644
--- a/x-pack/plugins/ingest_manager/server/plugin.ts
+++ b/x-pack/plugins/ingest_manager/server/plugin.ts
@@ -45,13 +45,14 @@ import {
registerSettingsRoutes,
registerAppRoutes,
} from './routes';
-import { IngestManagerConfigType } from '../common';
+import { IngestManagerConfigType, NewDatasource } from '../common';
import {
appContextService,
licenseService,
ESIndexPatternSavedObjectService,
ESIndexPatternService,
AgentService,
+ datasourceService,
} from './services';
import { getAgentStatusById } from './services/agents';
import { CloudSetup } from '../../cloud/server';
@@ -92,12 +93,31 @@ const allSavedObjectTypes = [
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
];
+/**
+ * Callbacks supported by the Ingest plugin
+ */
+export type ExternalCallback = [
+ 'datasourceCreate',
+ (newDatasource: NewDatasource) => Promise
+];
+
+export type ExternalCallbacksStorage = Map>;
+
/**
* Describes public IngestManager plugin contract returned at the `startup` stage.
*/
export interface IngestManagerStartContract {
esIndexPatternService: ESIndexPatternService;
agentService: AgentService;
+ /**
+ * Services for Ingest's Datasources
+ */
+ datasourceService: typeof datasourceService;
+ /**
+ * Register callbacks for inclusion in ingest API processing
+ * @param args
+ */
+ registerExternalCallback: (...args: ExternalCallback) => void;
}
export class IngestManagerPlugin
@@ -237,6 +257,10 @@ export class IngestManagerPlugin
agentService: {
getAgentStatusById,
},
+ datasourceService,
+ registerExternalCallback: (...args: ExternalCallback) => {
+ return appContextService.addExternalCallback(...args);
+ },
};
}
diff --git a/x-pack/plugins/ingest_manager/server/routes/datasource/datasource_handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/datasource/datasource_handlers.test.ts
new file mode 100644
index 0000000000000..07cbeb8b2cec5
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/routes/datasource/datasource_handlers.test.ts
@@ -0,0 +1,332 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { httpServerMock, httpServiceMock } from 'src/core/server/mocks';
+import { IRouter, KibanaRequest, Logger, RequestHandler, RouteConfig } from 'kibana/server';
+import { registerRoutes } from './index';
+import { DATASOURCE_API_ROUTES } from '../../../common/constants';
+import { xpackMocks } from '../../../../../mocks';
+import { appContextService } from '../../services';
+import { createAppContextStartContractMock } from '../../mocks';
+import { DatasourceServiceInterface, ExternalCallback } from '../..';
+import { CreateDatasourceRequestSchema } from '../../types/rest_spec';
+import { datasourceService } from '../../services';
+
+const datasourceServiceMock = datasourceService as jest.Mocked;
+
+jest.mock('../../services/datasource', (): {
+ datasourceService: jest.Mocked;
+} => {
+ return {
+ datasourceService: {
+ assignPackageStream: jest.fn((packageInfo, dataInputs) => Promise.resolve(dataInputs)),
+ buildDatasourceFromPackage: jest.fn(),
+ bulkCreate: jest.fn(),
+ create: jest.fn((soClient, newData) =>
+ Promise.resolve({
+ ...newData,
+ id: '1',
+ revision: 1,
+ updated_at: new Date().toISOString(),
+ updated_by: 'elastic',
+ created_at: new Date().toISOString(),
+ created_by: 'elastic',
+ })
+ ),
+ delete: jest.fn(),
+ get: jest.fn(),
+ getByIDs: jest.fn(),
+ list: jest.fn(),
+ update: jest.fn(),
+ },
+ };
+});
+
+jest.mock('../../services/epm/packages', () => {
+ return {
+ ensureInstalledPackage: jest.fn(() => Promise.resolve()),
+ getPackageInfo: jest.fn(() => Promise.resolve()),
+ };
+});
+
+describe('When calling datasource', () => {
+ let routerMock: jest.Mocked;
+ let routeHandler: RequestHandler;
+ let routeConfig: RouteConfig;
+ let context: ReturnType;
+ let response: ReturnType;
+
+ beforeAll(() => {
+ routerMock = httpServiceMock.createRouter();
+ registerRoutes(routerMock);
+ });
+
+ beforeEach(() => {
+ appContextService.start(createAppContextStartContractMock());
+ context = xpackMocks.createRequestHandlerContext();
+ response = httpServerMock.createResponseFactory();
+ });
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ appContextService.stop();
+ });
+
+ describe('create api handler', () => {
+ const getCreateKibanaRequest = (
+ newData?: typeof CreateDatasourceRequestSchema.body
+ ): KibanaRequest => {
+ return httpServerMock.createKibanaRequest<
+ undefined,
+ undefined,
+ typeof CreateDatasourceRequestSchema.body
+ >({
+ path: routeConfig.path,
+ method: 'post',
+ body: newData || {
+ name: 'endpoint-1',
+ description: '',
+ config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
+ enabled: true,
+ output_id: '',
+ inputs: [],
+ namespace: 'default',
+ package: { name: 'endpoint', title: 'Elastic Endpoint', version: '0.5.0' },
+ },
+ });
+ };
+
+ // Set the routeConfig and routeHandler to the Create API
+ beforeAll(() => {
+ [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) =>
+ path.startsWith(DATASOURCE_API_ROUTES.CREATE_PATTERN)
+ )!;
+ });
+
+ describe('and external callbacks are registered', () => {
+ const callbackCallingOrder: string[] = [];
+
+ // Callback one adds an input that includes a `config` property
+ const callbackOne: ExternalCallback[1] = jest.fn(async (ds) => {
+ callbackCallingOrder.push('one');
+ const newDs = {
+ ...ds,
+ inputs: [
+ {
+ type: 'endpoint',
+ enabled: true,
+ streams: [],
+ config: {
+ one: {
+ value: 'inserted by callbackOne',
+ },
+ },
+ },
+ ],
+ };
+ return newDs;
+ });
+
+ // Callback two adds an additional `input[0].config` property
+ const callbackTwo: ExternalCallback[1] = jest.fn(async (ds) => {
+ callbackCallingOrder.push('two');
+ const newDs = {
+ ...ds,
+ inputs: [
+ {
+ ...ds.inputs[0],
+ config: {
+ ...ds.inputs[0].config,
+ two: {
+ value: 'inserted by callbackTwo',
+ },
+ },
+ },
+ ],
+ };
+ return newDs;
+ });
+
+ beforeEach(() => {
+ appContextService.addExternalCallback('datasourceCreate', callbackOne);
+ appContextService.addExternalCallback('datasourceCreate', callbackTwo);
+ });
+
+ afterEach(() => (callbackCallingOrder.length = 0));
+
+ it('should call external callbacks in expected order', async () => {
+ const request = getCreateKibanaRequest();
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalled();
+ expect(callbackCallingOrder).toEqual(['one', 'two']);
+ });
+
+ it('should feed datasource returned by last callback', async () => {
+ const request = getCreateKibanaRequest();
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalled();
+ expect(callbackOne).toHaveBeenCalledWith({
+ config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
+ description: '',
+ enabled: true,
+ inputs: [],
+ name: 'endpoint-1',
+ namespace: 'default',
+ output_id: '',
+ package: {
+ name: 'endpoint',
+ title: 'Elastic Endpoint',
+ version: '0.5.0',
+ },
+ });
+ expect(callbackTwo).toHaveBeenCalledWith({
+ config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
+ description: '',
+ enabled: true,
+ inputs: [
+ {
+ type: 'endpoint',
+ enabled: true,
+ streams: [],
+ config: {
+ one: {
+ value: 'inserted by callbackOne',
+ },
+ },
+ },
+ ],
+ name: 'endpoint-1',
+ namespace: 'default',
+ output_id: '',
+ package: {
+ name: 'endpoint',
+ title: 'Elastic Endpoint',
+ version: '0.5.0',
+ },
+ });
+ });
+
+ it('should create with data from callback', async () => {
+ const request = getCreateKibanaRequest();
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalled();
+ expect(datasourceServiceMock.create.mock.calls[0][1]).toEqual({
+ config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
+ description: '',
+ enabled: true,
+ inputs: [
+ {
+ config: {
+ one: {
+ value: 'inserted by callbackOne',
+ },
+ two: {
+ value: 'inserted by callbackTwo',
+ },
+ },
+ enabled: true,
+ streams: [],
+ type: 'endpoint',
+ },
+ ],
+ name: 'endpoint-1',
+ namespace: 'default',
+ output_id: '',
+ package: {
+ name: 'endpoint',
+ title: 'Elastic Endpoint',
+ version: '0.5.0',
+ },
+ });
+ });
+
+ describe('and a callback throws an exception', () => {
+ const callbackThree: ExternalCallback[1] = jest.fn(async (ds) => {
+ callbackCallingOrder.push('three');
+ throw new Error('callbackThree threw error on purpose');
+ });
+
+ const callbackFour: ExternalCallback[1] = jest.fn(async (ds) => {
+ callbackCallingOrder.push('four');
+ return {
+ ...ds,
+ inputs: [
+ {
+ ...ds.inputs[0],
+ config: {
+ ...ds.inputs[0].config,
+ four: {
+ value: 'inserted by callbackFour',
+ },
+ },
+ },
+ ],
+ };
+ });
+
+ beforeEach(() => {
+ appContextService.addExternalCallback('datasourceCreate', callbackThree);
+ appContextService.addExternalCallback('datasourceCreate', callbackFour);
+ });
+
+ it('should skip over callback exceptions and still execute other callbacks', async () => {
+ const request = getCreateKibanaRequest();
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalled();
+ expect(callbackCallingOrder).toEqual(['one', 'two', 'three', 'four']);
+ });
+
+ it('should log errors', async () => {
+ const errorLogger = (appContextService.getLogger() as jest.Mocked).error;
+ const request = getCreateKibanaRequest();
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalled();
+ expect(errorLogger.mock.calls).toEqual([
+ ['An external registered [datasourceCreate] callback failed when executed'],
+ [new Error('callbackThree threw error on purpose')],
+ ]);
+ });
+
+ it('should create datasource with last successful returned datasource', async () => {
+ const request = getCreateKibanaRequest();
+ await routeHandler(context, request, response);
+ expect(response.ok).toHaveBeenCalled();
+ expect(datasourceServiceMock.create.mock.calls[0][1]).toEqual({
+ config_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
+ description: '',
+ enabled: true,
+ inputs: [
+ {
+ config: {
+ one: {
+ value: 'inserted by callbackOne',
+ },
+ two: {
+ value: 'inserted by callbackTwo',
+ },
+ four: {
+ value: 'inserted by callbackFour',
+ },
+ },
+ enabled: true,
+ streams: [],
+ type: 'endpoint',
+ },
+ ],
+ name: 'endpoint-1',
+ namespace: 'default',
+ output_id: '',
+ package: {
+ name: 'endpoint',
+ title: 'Elastic Endpoint',
+ version: '0.5.0',
+ },
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts
index 09daec3370400..4f83d24a846ea 100644
--- a/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/datasource/handlers.ts
@@ -14,6 +14,7 @@ import {
CreateDatasourceRequestSchema,
UpdateDatasourceRequestSchema,
DeleteDatasourcesRequestSchema,
+ NewDatasource,
} from '../../types';
import { CreateDatasourceResponse, DeleteDatasourcesResponse } from '../../../common';
@@ -76,23 +77,50 @@ export const createDatasourceHandler: RequestHandler<
const soClient = context.core.savedObjects.client;
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
- const newData = { ...request.body };
+ const logger = appContextService.getLogger();
+ let newData = { ...request.body };
try {
+ // If we have external callbacks, then process those now before creating the actual datasource
+ const externalCallbacks = appContextService.getExternalCallbacks('datasourceCreate');
+ if (externalCallbacks && externalCallbacks.size > 0) {
+ let updatedNewData: NewDatasource = newData;
+
+ for (const callback of externalCallbacks) {
+ try {
+ // ensure that the returned value by the callback passes schema validation
+ updatedNewData = CreateDatasourceRequestSchema.body.validate(
+ await callback(updatedNewData)
+ );
+ } catch (error) {
+ // Log the error, but keep going and process the other callbacks
+ logger.error('An external registered [datasourceCreate] callback failed when executed');
+ logger.error(error);
+ }
+ }
+
+ // The type `NewDatasource` and the `DatasourceBaseSchema` are incompatible.
+ // `NewDatasrouce` defines `namespace` as optional string, which means that `undefined` is a
+ // valid value, however, the schema defines it as string with a minimum length of 1.
+ // Here, we need to cast the value back to the schema type and ignore the TS error.
+ // @ts-ignore
+ newData = updatedNewData as typeof CreateDatasourceRequestSchema.body;
+ }
+
// Make sure the datasource package is installed
- if (request.body.package?.name) {
+ if (newData.package?.name) {
await ensureInstalledPackage({
savedObjectsClient: soClient,
- pkgName: request.body.package.name,
+ pkgName: newData.package.name,
callCluster,
});
const pkgInfo = await getPackageInfo({
savedObjectsClient: soClient,
- pkgName: request.body.package.name,
- pkgVersion: request.body.package.version,
+ pkgName: newData.package.name,
+ pkgVersion: newData.package.version,
});
newData.inputs = (await datasourceService.assignPackageStream(
pkgInfo,
- request.body.inputs
+ newData.inputs
)) as TypeOf['inputs'];
}
@@ -103,6 +131,7 @@ export const createDatasourceHandler: RequestHandler<
body,
});
} catch (e) {
+ logger.error(e);
return response.customError({
statusCode: 500,
body: { message: e.message },
diff --git a/x-pack/plugins/ingest_manager/server/services/app_context.ts b/x-pack/plugins/ingest_manager/server/services/app_context.ts
index 5ed6f7c5e54d1..4d109b73d12d9 100644
--- a/x-pack/plugins/ingest_manager/server/services/app_context.ts
+++ b/x-pack/plugins/ingest_manager/server/services/app_context.ts
@@ -12,7 +12,7 @@ import {
} from '../../../encrypted_saved_objects/server';
import { SecurityPluginSetup } from '../../../security/server';
import { IngestManagerConfigType } from '../../common';
-import { IngestManagerAppContext } from '../plugin';
+import { ExternalCallback, ExternalCallbacksStorage, IngestManagerAppContext } from '../plugin';
import { CloudSetup } from '../../../cloud/server';
class AppContextService {
@@ -27,6 +27,7 @@ class AppContextService {
private cloud?: CloudSetup;
private logger: Logger | undefined;
private httpSetup?: HttpServiceSetup;
+ private externalCallbacks: ExternalCallbacksStorage = new Map();
public async start(appContext: IngestManagerAppContext) {
this.encryptedSavedObjects = appContext.encryptedSavedObjectsStart?.getClient();
@@ -47,7 +48,9 @@ class AppContextService {
}
}
- public stop() {}
+ public stop() {
+ this.externalCallbacks.clear();
+ }
public getEncryptedSavedObjects() {
if (!this.encryptedSavedObjects) {
@@ -121,6 +124,19 @@ class AppContextService {
}
return this.kibanaVersion;
}
+
+ public addExternalCallback(type: ExternalCallback[0], callback: ExternalCallback[1]) {
+ if (!this.externalCallbacks.has(type)) {
+ this.externalCallbacks.set(type, new Set());
+ }
+ this.externalCallbacks.get(type)!.add(callback);
+ }
+
+ public getExternalCallbacks(type: ExternalCallback[0]) {
+ if (this.externalCallbacks) {
+ return this.externalCallbacks.get(type);
+ }
+ }
}
export const appContextService = new AppContextService();
diff --git a/x-pack/plugins/ingest_manager/server/services/datasource.ts b/x-pack/plugins/ingest_manager/server/services/datasource.ts
index 3ad94ea8191d4..f3f460d2a7420 100644
--- a/x-pack/plugins/ingest_manager/server/services/datasource.ts
+++ b/x-pack/plugins/ingest_manager/server/services/datasource.ts
@@ -307,4 +307,5 @@ async function _assignPackageStreamToStream(
return { ...stream };
}
+export type DatasourceServiceInterface = DatasourceService;
export const datasourceService = new DatasourceService();
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts
index ec0c526482b45..899f85ecdea30 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts
@@ -17,7 +17,6 @@ import {
sendPutDatasource,
} from '../policy_list/services/ingest';
import { NewPolicyData, PolicyData } from '../../../../../../common/endpoint/types';
-import { factory as policyConfigFactory } from '../../../../../../common/endpoint/models/policy_config';
import { ImmutableMiddlewareFactory } from '../../../../../common/store';
export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory = (
@@ -43,23 +42,6 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory {
routerMock = httpServiceMock.createRouter();
endpointAppContextService = new EndpointAppContextService();
- endpointAppContextService.start({
- agentService: createMockAgentService(),
- });
+ endpointAppContextService.start(createMockEndpointAppContextServiceStartContract());
registerAlertRoutes(routerMock, {
logFactory: loggingSystemMock.create(),
diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts
index cb8c913a73b8e..7b8a368b6c975 100644
--- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts
@@ -3,7 +3,15 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
-import { AgentService } from '../../../ingest_manager/server';
+import { AgentService, IngestManagerStartContract } from '../../../ingest_manager/server';
+import { handleDatasourceCreate } from './ingest_integration';
+
+export type EndpointAppContextServiceStartContract = Pick<
+ IngestManagerStartContract,
+ 'agentService'
+> & {
+ registerIngestCallback: IngestManagerStartContract['registerExternalCallback'];
+};
/**
* A singleton that holds shared services that are initialized during the start up phase
@@ -12,8 +20,9 @@ import { AgentService } from '../../../ingest_manager/server';
export class EndpointAppContextService {
private agentService: AgentService | undefined;
- public start(dependencies: { agentService: AgentService }) {
+ public start(dependencies: EndpointAppContextServiceStartContract) {
this.agentService = dependencies.agentService;
+ dependencies.registerIngestCallback('datasourceCreate', handleDatasourceCreate);
}
public stop() {}
diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts
new file mode 100644
index 0000000000000..6ff0949311587
--- /dev/null
+++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config';
+import { NewPolicyData } from '../../common/endpoint/types';
+import { NewDatasource } from '../../../ingest_manager/common/types/models';
+
+/**
+ * Callback to handle creation of Datasources in Ingest Manager
+ * @param newDatasource
+ */
+export const handleDatasourceCreate = async (
+ newDatasource: NewDatasource
+): Promise => {
+ // We only care about Endpoint datasources
+ if (newDatasource.package?.name !== 'endpoint') {
+ return newDatasource;
+ }
+
+ // We cast the type here so that any changes to the Endpoint specific data
+ // follow the types/schema expected
+ let updatedDatasource = newDatasource as NewPolicyData;
+
+ // Until we get the Default Policy Configuration in the Endpoint package,
+ // we will add it here manually at creation time.
+ // @ts-ignore
+ if (newDatasource.inputs.length === 0) {
+ updatedDatasource = {
+ ...newDatasource,
+ inputs: [
+ {
+ type: 'endpoint',
+ enabled: true,
+ streams: [],
+ config: {
+ policy: {
+ value: policyConfigFactory(),
+ },
+ },
+ },
+ ],
+ };
+ }
+
+ return updatedDatasource;
+};
diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts
index b10e9e4dc90e7..5435eff4ef150 100644
--- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts
@@ -6,7 +6,28 @@
import { IScopedClusterClient, SavedObjectsClientContract } from 'kibana/server';
import { xpackMocks } from '../../../../mocks';
-import { AgentService, IngestManagerStartContract } from '../../../ingest_manager/server';
+import {
+ AgentService,
+ IngestManagerStartContract,
+ ExternalCallback,
+} from '../../../ingest_manager/server';
+import { EndpointAppContextServiceStartContract } from './endpoint_app_context_services';
+import { createDatasourceServiceMock } from '../../../ingest_manager/server/mocks';
+
+/**
+ * Crates a mocked input contract for the `EndpointAppContextService#start()` method
+ */
+export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked<
+ EndpointAppContextServiceStartContract
+> => {
+ return {
+ agentService: createMockAgentService(),
+ registerIngestCallback: jest.fn<
+ ReturnType,
+ Parameters
+ >(),
+ };
+};
/**
* Creates a mock AgentService
@@ -32,6 +53,8 @@ export const createMockIngestManagerStartContract = (
getESIndexPattern: jest.fn().mockResolvedValue(indexPattern),
},
agentService: createMockAgentService(),
+ registerExternalCallback: jest.fn((...args: ExternalCallback) => {}),
+ datasourceService: createDatasourceServiceMock(),
};
};
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
index ba51a3b6aa92e..c04975fa8b28e 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
@@ -27,8 +27,10 @@ import {
} from '../../../../common/endpoint/types';
import { SearchResponse } from 'elasticsearch';
import { registerEndpointRoutes } from './index';
-import { createMockAgentService, createRouteHandlerContext } from '../../mocks';
-import { AgentService } from '../../../../../ingest_manager/server';
+import {
+ createMockEndpointAppContextServiceStartContract,
+ createRouteHandlerContext,
+} from '../../mocks';
import Boom from 'boom';
import { EndpointAppContextService } from '../../endpoint_app_context_services';
import { createMockConfig } from '../../../lib/detection_engine/routes/__mocks__';
@@ -44,7 +46,9 @@ describe('test endpoint route', () => {
let routeHandler: RequestHandler;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let routeConfig: RouteConfig;
- let mockAgentService: jest.Mocked;
+ let mockAgentService: ReturnType<
+ typeof createMockEndpointAppContextServiceStartContract
+ >['agentService'];
let endpointAppContextService: EndpointAppContextService;
beforeEach(() => {
@@ -56,11 +60,10 @@ describe('test endpoint route', () => {
mockClusterClient.asScoped.mockReturnValue(mockScopedClient);
routerMock = httpServiceMock.createRouter();
mockResponse = httpServerMock.createResponseFactory();
- mockAgentService = createMockAgentService();
endpointAppContextService = new EndpointAppContextService();
- endpointAppContextService.start({
- agentService: mockAgentService,
- });
+ const startContract = createMockEndpointAppContextServiceStartContract();
+ endpointAppContextService.start(startContract);
+ mockAgentService = startContract.agentService;
registerEndpointRoutes(routerMock, {
logFactory: loggingSystemMock.create(),
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts
index 6c1f0a206ffaa..16af3a95bc72d 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts
@@ -4,7 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EndpointAppContextService } from '../../endpoint_app_context_services';
-import { createMockAgentService, createRouteHandlerContext } from '../../mocks';
+import {
+ createMockEndpointAppContextServiceStartContract,
+ createRouteHandlerContext,
+} from '../../mocks';
import { getHostPolicyResponseHandler } from './handlers';
import {
IScopedClusterClient,
@@ -17,7 +20,6 @@ import {
loggingSystemMock,
savedObjectsClientMock,
} from '../../../../../../../src/core/server/mocks';
-import { AgentService } from '../../../../../ingest_manager/server/services';
import { SearchResponse } from 'elasticsearch';
import { GetHostPolicyResponse, HostPolicyResponse } from '../../../../common/endpoint/types';
import { EndpointDocGenerator } from '../../../../common/endpoint/generate_data';
@@ -28,17 +30,13 @@ describe('test policy response handler', () => {
let mockScopedClient: jest.Mocked;
let mockSavedObjectClient: jest.Mocked;
let mockResponse: jest.Mocked;
- let mockAgentService: jest.Mocked;
beforeEach(() => {
mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
mockSavedObjectClient = savedObjectsClientMock.create();
mockResponse = httpServerMock.createResponseFactory();
endpointAppContextService = new EndpointAppContextService();
- mockAgentService = createMockAgentService();
- endpointAppContextService.start({
- agentService: mockAgentService,
- });
+ endpointAppContextService.start(createMockEndpointAppContextServiceStartContract());
});
afterEach(() => endpointAppContextService.stop());
diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts
index 9fe7307e8cb6d..879c132ddec54 100644
--- a/x-pack/plugins/security_solution/server/plugin.ts
+++ b/x-pack/plugins/security_solution/server/plugin.ts
@@ -219,7 +219,9 @@ export class Plugin implements IPlugin
Date: Wed, 24 Jun 2020 20:33:58 -0400
Subject: [PATCH 19/82] [IngestManager] Expose agent authentication using
access key (#69650) (#69850)
* [IngestManager] Expose agent authentication using access key
* Add unit tests to authenticateAgentWithAccessToken service
Co-authored-by: Nicolas Chaulet
---
.../plugins/ingest_manager/server/plugin.ts | 3 +-
.../server/routes/agent/acks_handlers.test.ts | 2 +-
.../server/routes/agent/acks_handlers.ts | 4 +-
.../server/routes/agent/handlers.ts | 3 +-
.../server/routes/agent/index.ts | 2 +-
.../server/services/agents/acks.ts | 4 +-
.../services/agents/authenticate.test.ts | 154 ++++++++++++++++++
.../server/services/agents/authenticate.ts | 30 ++++
.../server/services/agents/index.ts | 1 +
9 files changed, 193 insertions(+), 10 deletions(-)
create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/authenticate.test.ts
create mode 100644 x-pack/plugins/ingest_manager/server/services/agents/authenticate.ts
diff --git a/x-pack/plugins/ingest_manager/server/plugin.ts b/x-pack/plugins/ingest_manager/server/plugin.ts
index 6cb448bbfa959..89f3c651eb71a 100644
--- a/x-pack/plugins/ingest_manager/server/plugin.ts
+++ b/x-pack/plugins/ingest_manager/server/plugin.ts
@@ -54,7 +54,7 @@ import {
AgentService,
datasourceService,
} from './services';
-import { getAgentStatusById } from './services/agents';
+import { getAgentStatusById, authenticateAgentWithAccessToken } from './services/agents';
import { CloudSetup } from '../../cloud/server';
import { agentCheckinState } from './services/agents/checkin/state';
@@ -256,6 +256,7 @@ export class IngestManagerPlugin
esIndexPatternService: new ESIndexPatternSavedObjectService(),
agentService: {
getAgentStatusById,
+ authenticateAgentWithAccessToken,
},
datasourceService,
registerExternalCallback: (...args: ExternalCallback) => {
diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts
index 84923d5c33664..aaed189ae3ddd 100644
--- a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.test.ts
@@ -77,7 +77,7 @@ describe('test acks handlers', () => {
id: 'action1',
},
]),
- getAgentByAccessAPIKeyId: jest.fn().mockReturnValueOnce({
+ authenticateAgentWithAccessToken: jest.fn().mockReturnValueOnce({
id: 'agent',
}),
getSavedObjectsClientContract: jest.fn().mockReturnValueOnce(mockSavedObjectsClient),
diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts
index 83d894295c312..0b719d8a67df7 100644
--- a/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/agent/acks_handlers.ts
@@ -9,7 +9,6 @@
import { RequestHandler } from 'kibana/server';
import { TypeOf } from '@kbn/config-schema';
import { PostAgentAcksRequestSchema } from '../../types/rest_spec';
-import * as APIKeyService from '../../services/api_keys';
import { AcksService } from '../../services/agents';
import { AgentEvent } from '../../../common/types/models';
import { PostAgentAcksResponse } from '../../../common/types/rest_spec';
@@ -24,8 +23,7 @@ export const postAgentAcksHandlerBuilder = function (
return async (context, request, response) => {
try {
const soClient = ackService.getSavedObjectsClientContract(request);
- const res = APIKeyService.parseApiKeyFromHeaders(request.headers);
- const agent = await ackService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId as string);
+ const agent = await ackService.authenticateAgentWithAccessToken(soClient, request);
const agentEvents = request.body.events as AgentEvent[];
// validate that all events are for the authorized agent obtained from the api key
diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
index 0d1c77b8d697f..d31498599a2b6 100644
--- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts
@@ -171,8 +171,7 @@ export const postAgentCheckinHandler: RequestHandler<
> = async (context, request, response) => {
try {
const soClient = appContextService.getInternalUserSOClient(request);
- const res = APIKeyService.parseApiKeyFromHeaders(request.headers);
- const agent = await AgentService.getAgentByAccessAPIKeyId(soClient, res.apiKeyId);
+ const agent = await AgentService.authenticateAgentWithAccessToken(soClient, request);
const abortController = new AbortController();
request.events.aborted$.subscribe(() => {
abortController.abort();
diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts
index 87eee4622c80b..eaab46c7b455c 100644
--- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts
+++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts
@@ -109,7 +109,7 @@ export const registerRoutes = (router: IRouter) => {
},
postAgentAcksHandlerBuilder({
acknowledgeAgentActions: AgentService.acknowledgeAgentActions,
- getAgentByAccessAPIKeyId: AgentService.getAgentByAccessAPIKeyId,
+ authenticateAgentWithAccessToken: AgentService.authenticateAgentWithAccessToken,
getSavedObjectsClientContract: appContextService.getInternalUserSOClient.bind(
appContextService
),
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts
index 81ba9754e8aa4..a1b48a879bb89 100644
--- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts
+++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts
@@ -140,9 +140,9 @@ export interface AcksService {
actionIds: AgentEvent[]
) => Promise;
- getAgentByAccessAPIKeyId: (
+ authenticateAgentWithAccessToken: (
soClient: SavedObjectsClientContract,
- accessAPIKeyId: string
+ request: KibanaRequest
) => Promise;
getSavedObjectsClientContract: (kibanaRequest: KibanaRequest) => SavedObjectsClientContract;
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/authenticate.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/authenticate.test.ts
new file mode 100644
index 0000000000000..b56ca4ca8cc17
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/services/agents/authenticate.test.ts
@@ -0,0 +1,154 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { KibanaRequest } from 'kibana/server';
+import { savedObjectsClientMock } from 'src/core/server/mocks';
+
+import { authenticateAgentWithAccessToken } from './authenticate';
+
+describe('test agent autenticate services', () => {
+ it('should succeed with a valid API key and an active agent', async () => {
+ const mockSavedObjectsClient = savedObjectsClientMock.create();
+ mockSavedObjectsClient.find.mockReturnValue(
+ Promise.resolve({
+ page: 1,
+ per_page: 100,
+ total: 1,
+ saved_objects: [
+ {
+ id: 'agent1',
+ type: 'agent',
+ references: [],
+ score: 0,
+ attributes: {
+ active: true,
+ access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
+ },
+ },
+ ],
+ })
+ );
+ await authenticateAgentWithAccessToken(mockSavedObjectsClient, {
+ auth: { isAuthenticated: true },
+ headers: {
+ authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
+ },
+ } as KibanaRequest);
+ });
+
+ it('should throw if the request is not authenticated', async () => {
+ const mockSavedObjectsClient = savedObjectsClientMock.create();
+ mockSavedObjectsClient.find.mockReturnValue(
+ Promise.resolve({
+ page: 1,
+ per_page: 100,
+ total: 1,
+ saved_objects: [
+ {
+ id: 'agent1',
+ type: 'agent',
+ references: [],
+ score: 0,
+ attributes: {
+ active: true,
+ access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
+ },
+ },
+ ],
+ })
+ );
+ expect(
+ authenticateAgentWithAccessToken(mockSavedObjectsClient, {
+ auth: { isAuthenticated: false },
+ headers: {
+ authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
+ },
+ } as KibanaRequest)
+ ).rejects.toThrow(/Request not authenticated/);
+ });
+
+ it('should throw if the ApiKey headers is malformed', async () => {
+ const mockSavedObjectsClient = savedObjectsClientMock.create();
+ mockSavedObjectsClient.find.mockReturnValue(
+ Promise.resolve({
+ page: 1,
+ per_page: 100,
+ total: 1,
+ saved_objects: [
+ {
+ id: 'agent1',
+ type: 'agent',
+ references: [],
+ score: 0,
+ attributes: {
+ active: false,
+ access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
+ },
+ },
+ ],
+ })
+ );
+ expect(
+ authenticateAgentWithAccessToken(mockSavedObjectsClient, {
+ auth: { isAuthenticated: true },
+ headers: {
+ authorization: 'aaaa',
+ },
+ } as KibanaRequest)
+ ).rejects.toThrow(/Authorization header is malformed/);
+ });
+
+ it('should throw if the agent is not active', async () => {
+ const mockSavedObjectsClient = savedObjectsClientMock.create();
+ mockSavedObjectsClient.find.mockReturnValue(
+ Promise.resolve({
+ page: 1,
+ per_page: 100,
+ total: 1,
+ saved_objects: [
+ {
+ id: 'agent1',
+ type: 'agent',
+ references: [],
+ score: 0,
+ attributes: {
+ active: false,
+ access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
+ },
+ },
+ ],
+ })
+ );
+ expect(
+ authenticateAgentWithAccessToken(mockSavedObjectsClient, {
+ auth: { isAuthenticated: true },
+ headers: {
+ authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
+ },
+ } as KibanaRequest)
+ ).rejects.toThrow(/Agent inactive/);
+ });
+
+ it('should throw if there is no agent matching the API key', async () => {
+ const mockSavedObjectsClient = savedObjectsClientMock.create();
+ mockSavedObjectsClient.find.mockReturnValue(
+ Promise.resolve({
+ page: 1,
+ per_page: 100,
+ total: 1,
+ saved_objects: [],
+ })
+ );
+ expect(
+ authenticateAgentWithAccessToken(mockSavedObjectsClient, {
+ auth: { isAuthenticated: true },
+ headers: {
+ authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
+ },
+ } as KibanaRequest)
+ ).rejects.toThrow(/Agent not found/);
+ });
+});
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/authenticate.ts b/x-pack/plugins/ingest_manager/server/services/agents/authenticate.ts
new file mode 100644
index 0000000000000..2515a02da4e78
--- /dev/null
+++ b/x-pack/plugins/ingest_manager/server/services/agents/authenticate.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import Boom from 'boom';
+import { KibanaRequest, SavedObjectsClientContract } from 'src/core/server';
+import { Agent } from '../../types';
+import * as APIKeyService from '../api_keys';
+import { getAgentByAccessAPIKeyId } from './crud';
+
+export async function authenticateAgentWithAccessToken(
+ soClient: SavedObjectsClientContract,
+ request: KibanaRequest
+): Promise {
+ if (!request.auth.isAuthenticated) {
+ throw Boom.unauthorized('Request not authenticated');
+ }
+ let res: { apiKey: string; apiKeyId: string };
+ try {
+ res = APIKeyService.parseApiKeyFromHeaders(request.headers);
+ } catch (err) {
+ throw Boom.unauthorized(err.message);
+ }
+
+ const agent = await getAgentByAccessAPIKeyId(soClient, res.apiKeyId);
+
+ return agent;
+}
diff --git a/x-pack/plugins/ingest_manager/server/services/agents/index.ts b/x-pack/plugins/ingest_manager/server/services/agents/index.ts
index 257091af0ebd0..400c099af4e93 100644
--- a/x-pack/plugins/ingest_manager/server/services/agents/index.ts
+++ b/x-pack/plugins/ingest_manager/server/services/agents/index.ts
@@ -14,3 +14,4 @@ export * from './crud';
export * from './update';
export * from './actions';
export * from './reassign';
+export * from './authenticate';
From 8c27e763d77c5e8be7ff63ef269e20f46ef398c5 Mon Sep 17 00:00:00 2001
From: CJ Cenizal
Date: Wed, 24 Jun 2020 21:36:40 -0700
Subject: [PATCH 20/82] Add delete data stream action and detail panel (#68919)
(#69873)
Co-authored-by: Elastic Machine
Co-authored-by: Elastic Machine
---
.../helpers/http_requests.ts | 18 ++
.../helpers/setup_environment.tsx | 18 +-
.../home/data_streams_tab.helpers.ts | 116 ++++++++--
.../home/data_streams_tab.test.ts | 200 +++++++++++++-----
.../home/indices_tab.helpers.ts | 15 ++
.../home/indices_tab.test.ts | 21 +-
.../common/lib/data_stream_serialization.ts | 12 +-
.../index_management/common/lib/index.ts | 2 +-
x-pack/plugins/index_management/kibana.json | 3 +-
.../public/application/app_context.tsx | 6 +-
.../application/mount_management_section.ts | 5 +-
.../data_stream_detail_panel.tsx | 136 ++++++++----
.../data_stream_list/data_stream_list.tsx | 107 +++++++---
.../data_stream_table/data_stream_table.tsx | 86 ++++++--
.../delete_data_stream_confirmation_modal.tsx | 149 +++++++++++++
.../index.ts | 7 +
.../public/application/services/api.ts | 13 +-
.../plugins/index_management/public/plugin.ts | 7 +-
.../server/client/elasticsearch.ts | 14 ++
.../server/routes/api/data_streams/index.ts | 5 +-
.../api/data_streams/register_delete_route.ts | 52 +++++
.../api/data_streams/register_get_route.ts | 41 +++-
x-pack/plugins/ingest_manager/public/index.ts | 2 +-
.../plugins/ingest_manager/public/plugin.ts | 8 +-
24 files changed, 860 insertions(+), 183 deletions(-)
create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx
create mode 100644 x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/index.ts
create mode 100644 x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
index 56d76da522ac2..907c749f8ec0b 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/http_requests.ts
@@ -35,6 +35,22 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};
+ const setLoadDataStreamResponse = (response: HttpResponse = []) => {
+ server.respondWith('GET', `${API_BASE_PATH}/data_streams/:id`, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(response),
+ ]);
+ };
+
+ const setDeleteDataStreamResponse = (response: HttpResponse = []) => {
+ server.respondWith('POST', `${API_BASE_PATH}/delete_data_streams`, [
+ 200,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(response),
+ ]);
+ };
+
const setDeleteTemplateResponse = (response: HttpResponse = []) => {
server.respondWith('POST', `${API_BASE_PATH}/delete_index_templates`, [
200,
@@ -80,6 +96,8 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setLoadTemplatesResponse,
setLoadIndicesResponse,
setLoadDataStreamsResponse,
+ setLoadDataStreamResponse,
+ setDeleteDataStreamResponse,
setDeleteTemplateResponse,
setLoadTemplateResponse,
setCreateTemplateResponse,
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
index 0a49191fdb149..d85db94d4a970 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
+++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/setup_environment.tsx
@@ -8,6 +8,7 @@
import React from 'react';
import axios from 'axios';
import axiosXhrAdapter from 'axios/lib/adapters/xhr';
+import { merge } from 'lodash';
import {
notificationServiceMock,
@@ -33,7 +34,7 @@ export const services = {
services.uiMetricService.setup({ reportUiStats() {} } as any);
setExtensionsService(services.extensionsService);
setUiMetricService(services.uiMetricService);
-const appDependencies = { services, core: {}, plugins: {} } as any;
+const appDependencies = { services, core: { getUrlForApp: () => {} }, plugins: {} } as any;
export const setupEnvironment = () => {
// Mock initialization of services
@@ -51,8 +52,13 @@ export const setupEnvironment = () => {
};
};
-export const WithAppDependencies = (Comp: any) => (props: any) => (
-
-
-
-);
+export const WithAppDependencies = (Comp: any, overridingDependencies: any = {}) => (
+ props: any
+) => {
+ const mergedDependencies = merge({}, appDependencies, overridingDependencies);
+ return (
+
+
+
+ );
+};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
index 572889954db6a..ecea230ecab85 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.helpers.ts
@@ -5,6 +5,7 @@
*/
import { act } from 'react-dom/test-utils';
+import { ReactWrapper } from 'enzyme';
import {
registerTestBed,
@@ -17,27 +18,38 @@ import { IndexManagementHome } from '../../../public/application/sections/home';
import { indexManagementStore } from '../../../public/application/store'; // eslint-disable-line @kbn/eslint/no-restricted-paths
import { WithAppDependencies, services, TestSubjects } from '../helpers';
-const testBedConfig: TestBedConfig = {
- store: () => indexManagementStore(services as any),
- memoryRouter: {
- initialEntries: [`/indices`],
- componentRoutePath: `/:section(indices|data_streams|templates)`,
- },
- doMountAsync: true,
-};
-
-const initTestBed = registerTestBed(WithAppDependencies(IndexManagementHome), testBedConfig);
-
export interface DataStreamsTabTestBed extends TestBed {
actions: {
goToDataStreamsList: () => void;
clickEmptyPromptIndexTemplateLink: () => void;
clickReloadButton: () => void;
+ clickNameAt: (index: number) => void;
clickIndicesAt: (index: number) => void;
+ clickDeletActionAt: (index: number) => void;
+ clickConfirmDelete: () => void;
+ clickDeletDataStreamButton: () => void;
};
+ findDeleteActionAt: (index: number) => ReactWrapper;
+ findDeleteConfirmationModal: () => ReactWrapper;
+ findDetailPanel: () => ReactWrapper;
+ findDetailPanelTitle: () => string;
+ findEmptyPromptIndexTemplateLink: () => ReactWrapper;
}
-export const setup = async (): Promise => {
+export const setup = async (overridingDependencies: any = {}): Promise => {
+ const testBedConfig: TestBedConfig = {
+ store: () => indexManagementStore(services as any),
+ memoryRouter: {
+ initialEntries: [`/indices`],
+ componentRoutePath: `/:section(indices|data_streams|templates)`,
+ },
+ doMountAsync: true,
+ };
+
+ const initTestBed = registerTestBed(
+ WithAppDependencies(IndexManagementHome, overridingDependencies),
+ testBedConfig
+ );
const testBed = await initTestBed();
/**
@@ -48,15 +60,17 @@ export const setup = async (): Promise => {
testBed.find('data_streamsTab').simulate('click');
};
- const clickEmptyPromptIndexTemplateLink = async () => {
- const { find, component, router } = testBed;
-
+ const findEmptyPromptIndexTemplateLink = () => {
+ const { find } = testBed;
const templateLink = find('dataStreamsEmptyPromptTemplateLink');
+ return templateLink;
+ };
+ const clickEmptyPromptIndexTemplateLink = async () => {
+ const { component, router } = testBed;
await act(async () => {
- router.navigateTo(templateLink.props().href!);
+ router.navigateTo(findEmptyPromptIndexTemplateLink().props().href!);
});
-
component.update();
};
@@ -65,10 +79,15 @@ export const setup = async (): Promise => {
find('reloadButton').simulate('click');
};
- const clickIndicesAt = async (index: number) => {
- const { component, table, router } = testBed;
+ const findTestSubjectAt = (testSubject: string, index: number) => {
+ const { table } = testBed;
const { rows } = table.getMetaData('dataStreamTable');
- const indicesLink = findTestSubject(rows[index].reactWrapper, 'indicesLink');
+ return findTestSubject(rows[index].reactWrapper, testSubject);
+ };
+
+ const clickIndicesAt = async (index: number) => {
+ const { component, router } = testBed;
+ const indicesLink = findTestSubjectAt('indicesLink', index);
await act(async () => {
router.navigateTo(indicesLink.props().href!);
@@ -77,14 +96,71 @@ export const setup = async (): Promise => {
component.update();
};
+ const clickNameAt = async (index: number) => {
+ const { component, router } = testBed;
+ const nameLink = findTestSubjectAt('nameLink', index);
+
+ await act(async () => {
+ router.navigateTo(nameLink.props().href!);
+ });
+
+ component.update();
+ };
+
+ const findDeleteActionAt = findTestSubjectAt.bind(null, 'deleteDataStream');
+
+ const clickDeletActionAt = (index: number) => {
+ findDeleteActionAt(index).simulate('click');
+ };
+
+ const findDeleteConfirmationModal = () => {
+ const { find } = testBed;
+ return find('deleteDataStreamsConfirmation');
+ };
+
+ const clickConfirmDelete = async () => {
+ const modal = document.body.querySelector('[data-test-subj="deleteDataStreamsConfirmation"]');
+ const confirmButton: HTMLButtonElement | null = modal!.querySelector(
+ '[data-test-subj="confirmModalConfirmButton"]'
+ );
+
+ await act(async () => {
+ confirmButton!.click();
+ });
+ };
+
+ const clickDeletDataStreamButton = () => {
+ const { find } = testBed;
+ find('deleteDataStreamButton').simulate('click');
+ };
+
+ const findDetailPanel = () => {
+ const { find } = testBed;
+ return find('dataStreamDetailPanel');
+ };
+
+ const findDetailPanelTitle = () => {
+ const { find } = testBed;
+ return find('dataStreamDetailPanelTitle').text();
+ };
+
return {
...testBed,
actions: {
goToDataStreamsList,
clickEmptyPromptIndexTemplateLink,
clickReloadButton,
+ clickNameAt,
clickIndicesAt,
+ clickDeletActionAt,
+ clickConfirmDelete,
+ clickDeletDataStreamButton,
},
+ findDeleteActionAt,
+ findDeleteConfirmationModal,
+ findDetailPanel,
+ findDetailPanelTitle,
+ findEmptyPromptIndexTemplateLink,
};
};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
index efe2e2d0c74ae..dfcbb51869466 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/data_streams_tab.test.ts
@@ -19,61 +19,38 @@ describe('Data Streams tab', () => {
server.restore();
});
- beforeEach(async () => {
- httpRequestsMockHelpers.setLoadIndicesResponse([
- {
- health: '',
- status: '',
- primary: '',
- replica: '',
- documents: '',
- documents_deleted: '',
- size: '',
- primary_size: '',
- name: 'data-stream-index',
- data_stream: 'dataStream1',
- },
- {
- health: 'green',
- status: 'open',
- primary: 1,
- replica: 1,
- documents: 10000,
- documents_deleted: 100,
- size: '156kb',
- primary_size: '156kb',
- name: 'non-data-stream-index',
- },
- ]);
-
- await act(async () => {
- testBed = await setup();
- });
- });
-
describe('when there are no data streams', () => {
beforeEach(async () => {
- const { actions, component } = testBed;
-
+ httpRequestsMockHelpers.setLoadIndicesResponse([]);
httpRequestsMockHelpers.setLoadDataStreamsResponse([]);
httpRequestsMockHelpers.setLoadTemplatesResponse({ templates: [], legacyTemplates: [] });
+ });
+
+ test('displays an empty prompt', async () => {
+ testBed = await setup();
await act(async () => {
- actions.goToDataStreamsList();
+ testBed.actions.goToDataStreamsList();
});
+ const { exists, component } = testBed;
component.update();
- });
-
- test('displays an empty prompt', async () => {
- const { exists } = testBed;
expect(exists('sectionLoading')).toBe(false);
expect(exists('emptyPrompt')).toBe(true);
});
- test('goes to index templates tab when "Get started" link is clicked', async () => {
- const { actions, exists } = testBed;
+ test('when Ingest Manager is disabled, goes to index templates tab when "Get started" link is clicked', async () => {
+ testBed = await setup({
+ plugins: {},
+ });
+
+ await act(async () => {
+ testBed.actions.goToDataStreamsList();
+ });
+
+ const { actions, exists, component } = testBed;
+ component.update();
await act(async () => {
actions.clickEmptyPromptIndexTemplateLink();
@@ -81,32 +58,77 @@ describe('Data Streams tab', () => {
expect(exists('templateList')).toBe(true);
});
+
+ test('when Ingest Manager is enabled, links to Ingest Manager', async () => {
+ testBed = await setup({
+ plugins: { ingestManager: { hi: 'ok' } },
+ });
+
+ await act(async () => {
+ testBed.actions.goToDataStreamsList();
+ });
+
+ const { findEmptyPromptIndexTemplateLink, component } = testBed;
+ component.update();
+
+ // Assert against the text because the href won't be available, due to dependency upon our core mock.
+ expect(findEmptyPromptIndexTemplateLink().text()).toBe('Ingest Manager');
+ });
});
describe('when there are data streams', () => {
beforeEach(async () => {
- const { actions, component } = testBed;
+ httpRequestsMockHelpers.setLoadIndicesResponse([
+ {
+ health: '',
+ status: '',
+ primary: '',
+ replica: '',
+ documents: '',
+ documents_deleted: '',
+ size: '',
+ primary_size: '',
+ name: 'data-stream-index',
+ data_stream: 'dataStream1',
+ },
+ {
+ health: 'green',
+ status: 'open',
+ primary: 1,
+ replica: 1,
+ documents: 10000,
+ documents_deleted: 100,
+ size: '156kb',
+ primary_size: '156kb',
+ name: 'non-data-stream-index',
+ },
+ ]);
+
+ const dataStreamForDetailPanel = createDataStreamPayload('dataStream1');
httpRequestsMockHelpers.setLoadDataStreamsResponse([
- createDataStreamPayload('dataStream1'),
+ dataStreamForDetailPanel,
createDataStreamPayload('dataStream2'),
]);
+ httpRequestsMockHelpers.setLoadDataStreamResponse(dataStreamForDetailPanel);
+
+ testBed = await setup();
+
await act(async () => {
- actions.goToDataStreamsList();
+ testBed.actions.goToDataStreamsList();
});
- component.update();
+ testBed.component.update();
});
test('lists them in the table', async () => {
const { table } = testBed;
-
const { tableCellsValues } = table.getMetaData('dataStreamTable');
expect(tableCellsValues).toEqual([
- ['dataStream1', '1', '@timestamp', '1'],
- ['dataStream2', '1', '@timestamp', '1'],
+ ['', 'dataStream1', '1', ''],
+ ['', 'dataStream2', '1', ''],
]);
});
@@ -126,12 +148,90 @@ describe('Data Streams tab', () => {
test('clicking the indices count navigates to the backing indices', async () => {
const { table, actions } = testBed;
-
await actions.clickIndicesAt(0);
-
expect(table.getMetaData('indexTable').tableCellsValues).toEqual([
['', '', '', '', '', '', '', 'dataStream1'],
]);
});
+
+ describe('row actions', () => {
+ test('can delete', () => {
+ const { findDeleteActionAt } = testBed;
+ const deleteAction = findDeleteActionAt(0);
+ expect(deleteAction.length).toBe(1);
+ });
+ });
+
+ describe('deleting a data stream', () => {
+ test('shows a confirmation modal', async () => {
+ const {
+ actions: { clickDeletActionAt },
+ findDeleteConfirmationModal,
+ } = testBed;
+ clickDeletActionAt(0);
+ const confirmationModal = findDeleteConfirmationModal();
+ expect(confirmationModal).toBeDefined();
+ });
+
+ test('sends a request to the Delete API', async () => {
+ const {
+ actions: { clickDeletActionAt, clickConfirmDelete },
+ } = testBed;
+ clickDeletActionAt(0);
+
+ httpRequestsMockHelpers.setDeleteDataStreamResponse({
+ results: {
+ dataStreamsDeleted: ['dataStream1'],
+ errors: [],
+ },
+ });
+
+ await clickConfirmDelete();
+
+ const { method, url, requestBody } = server.requests[server.requests.length - 1];
+
+ expect(method).toBe('POST');
+ expect(url).toBe(`${API_BASE_PATH}/delete_data_streams`);
+ expect(JSON.parse(JSON.parse(requestBody).body)).toEqual({
+ dataStreams: ['dataStream1'],
+ });
+ });
+ });
+
+ describe('detail panel', () => {
+ test('opens when the data stream name in the table is clicked', async () => {
+ const { actions, findDetailPanel, findDetailPanelTitle } = testBed;
+ await actions.clickNameAt(0);
+ expect(findDetailPanel().length).toBe(1);
+ expect(findDetailPanelTitle()).toBe('dataStream1');
+ });
+
+ test('deletes the data stream when delete button is clicked', async () => {
+ const {
+ actions: { clickNameAt, clickDeletDataStreamButton, clickConfirmDelete },
+ } = testBed;
+
+ await clickNameAt(0);
+
+ clickDeletDataStreamButton();
+
+ httpRequestsMockHelpers.setDeleteDataStreamResponse({
+ results: {
+ dataStreamsDeleted: ['dataStream1'],
+ errors: [],
+ },
+ });
+
+ await clickConfirmDelete();
+
+ const { method, url, requestBody } = server.requests[server.requests.length - 1];
+
+ expect(method).toBe('POST');
+ expect(url).toBe(`${API_BASE_PATH}/delete_data_streams`);
+ expect(JSON.parse(JSON.parse(requestBody).body)).toEqual({
+ dataStreams: ['dataStream1'],
+ });
+ });
+ });
});
});
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
index f00348aacbf08..11ea29fd9b78c 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts
@@ -5,6 +5,7 @@
*/
import { act } from 'react-dom/test-utils';
+import { ReactWrapper } from 'enzyme';
import {
registerTestBed,
@@ -34,6 +35,8 @@ export interface IndicesTestBed extends TestBed {
clickIncludeHiddenIndicesToggle: () => void;
clickDataStreamAt: (index: number) => void;
};
+ findDataStreamDetailPanel: () => ReactWrapper;
+ findDataStreamDetailPanelTitle: () => string;
}
export const setup = async (): Promise => {
@@ -77,6 +80,16 @@ export const setup = async (): Promise => {
component.update();
};
+ const findDataStreamDetailPanel = () => {
+ const { find } = testBed;
+ return find('dataStreamDetailPanel');
+ };
+
+ const findDataStreamDetailPanelTitle = () => {
+ const { find } = testBed;
+ return find('dataStreamDetailPanelTitle').text();
+ };
+
return {
...testBed,
actions: {
@@ -85,5 +98,7 @@ export const setup = async (): Promise => {
clickIncludeHiddenIndicesToggle,
clickDataStreamAt,
},
+ findDataStreamDetailPanel,
+ findDataStreamDetailPanelTitle,
};
};
diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
index c2d955bb4dfce..3d6d94d165855 100644
--- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
+++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.test.ts
@@ -70,10 +70,10 @@ describe(' ', () => {
},
]);
- httpRequestsMockHelpers.setLoadDataStreamsResponse([
- createDataStreamPayload('dataStream1'),
- createDataStreamPayload('dataStream2'),
- ]);
+ // The detail panel should still appear even if there are no data streams.
+ httpRequestsMockHelpers.setLoadDataStreamsResponse([]);
+
+ httpRequestsMockHelpers.setLoadDataStreamResponse(createDataStreamPayload('dataStream1'));
testBed = await setup();
@@ -86,13 +86,16 @@ describe(' ', () => {
});
test('navigates to the data stream in the Data Streams tab', async () => {
- const { table, actions } = testBed;
+ const {
+ findDataStreamDetailPanel,
+ findDataStreamDetailPanelTitle,
+ actions: { clickDataStreamAt },
+ } = testBed;
- await actions.clickDataStreamAt(0);
+ await clickDataStreamAt(0);
- expect(table.getMetaData('dataStreamTable').tableCellsValues).toEqual([
- ['dataStream1', '1', '@timestamp', '1'],
- ]);
+ expect(findDataStreamDetailPanel().length).toBe(1);
+ expect(findDataStreamDetailPanelTitle()).toBe('dataStream1');
});
});
diff --git a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
index 9d267210a6b31..51528ed9856ce 100644
--- a/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
+++ b/x-pack/plugins/index_management/common/lib/data_stream_serialization.ts
@@ -6,8 +6,10 @@
import { DataStream, DataStreamFromEs } from '../types';
-export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] {
- return dataStreamsFromEs.map(({ name, timestamp_field, indices, generation }) => ({
+export function deserializeDataStream(dataStreamFromEs: DataStreamFromEs): DataStream {
+ const { name, timestamp_field, indices, generation } = dataStreamFromEs;
+
+ return {
name,
timeStampField: timestamp_field,
indices: indices.map(
@@ -17,5 +19,9 @@ export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[])
})
),
generation,
- }));
+ };
+}
+
+export function deserializeDataStreamList(dataStreamsFromEs: DataStreamFromEs[]): DataStream[] {
+ return dataStreamsFromEs.map((dataStream) => deserializeDataStream(dataStream));
}
diff --git a/x-pack/plugins/index_management/common/lib/index.ts b/x-pack/plugins/index_management/common/lib/index.ts
index fce4d8ccc2502..4e76a40ced524 100644
--- a/x-pack/plugins/index_management/common/lib/index.ts
+++ b/x-pack/plugins/index_management/common/lib/index.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export { deserializeDataStreamList } from './data_stream_serialization';
+export { deserializeDataStream, deserializeDataStreamList } from './data_stream_serialization';
export {
deserializeLegacyTemplateList,
diff --git a/x-pack/plugins/index_management/kibana.json b/x-pack/plugins/index_management/kibana.json
index 2e0fa04337b40..40ecb26e8f0c9 100644
--- a/x-pack/plugins/index_management/kibana.json
+++ b/x-pack/plugins/index_management/kibana.json
@@ -10,7 +10,8 @@
],
"optionalPlugins": [
"security",
- "usageCollection"
+ "usageCollection",
+ "ingestManager"
],
"configPath": ["xpack", "index_management"]
}
diff --git a/x-pack/plugins/index_management/public/application/app_context.tsx b/x-pack/plugins/index_management/public/application/app_context.tsx
index 84938de416941..c821907120373 100644
--- a/x-pack/plugins/index_management/public/application/app_context.tsx
+++ b/x-pack/plugins/index_management/public/application/app_context.tsx
@@ -6,9 +6,10 @@
import React, { createContext, useContext } from 'react';
import { ScopedHistory } from 'kibana/public';
-import { CoreStart } from '../../../../../src/core/public';
+import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
-import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public';
+import { CoreStart } from '../../../../../src/core/public';
+import { IngestManagerSetup } from '../../../ingest_manager/public';
import { IndexMgmtMetricsType } from '../types';
import { UiMetricService, NotificationService, HttpService } from './services';
import { ExtensionsService } from '../services';
@@ -22,6 +23,7 @@ export interface AppDependencies {
};
plugins: {
usageCollection: UsageCollectionSetup;
+ ingestManager?: IngestManagerSetup;
};
services: {
uiMetricService: UiMetricService;
diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts
index e8b6f200fb349..258f32865720a 100644
--- a/x-pack/plugins/index_management/public/application/mount_management_section.ts
+++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts
@@ -8,6 +8,7 @@ import { CoreSetup } from 'src/core/public';
import { ManagementAppMountParams } from 'src/plugins/management/public/';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/public';
+import { IngestManagerSetup } from '../../../ingest_manager/public';
import { ExtensionsService } from '../services';
import { IndexMgmtMetricsType } from '../types';
import { AppDependencies } from './app_context';
@@ -28,7 +29,8 @@ export async function mountManagementSection(
coreSetup: CoreSetup,
usageCollection: UsageCollectionSetup,
services: InternalServices,
- params: ManagementAppMountParams
+ params: ManagementAppMountParams,
+ ingestManager?: IngestManagerSetup
) {
const { element, setBreadcrumbs, history } = params;
const [core] = await coreSetup.getStartServices();
@@ -44,6 +46,7 @@ export async function mountManagementSection(
},
plugins: {
usageCollection,
+ ingestManager,
},
services,
history,
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
index a6c8b83a05f98..577f04a4a7efd 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_detail_panel/data_stream_detail_panel.tsx
@@ -4,9 +4,10 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { Fragment } from 'react';
+import React, { useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
+ EuiButton,
EuiFlyout,
EuiFlyoutHeader,
EuiTitle,
@@ -15,14 +16,18 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiButtonEmpty,
+ EuiDescriptionList,
+ EuiDescriptionListTitle,
+ EuiDescriptionListDescription,
} from '@elastic/eui';
import { SectionLoading, SectionError, Error } from '../../../../components';
import { useLoadDataStream } from '../../../../services/api';
+import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal';
interface Props {
dataStreamName: string;
- onClose: () => void;
+ onClose: (shouldReload?: boolean) => void;
}
/**
@@ -36,6 +41,8 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
}) => {
const { error, data: dataStream, isLoading } = useLoadDataStream(dataStreamName);
+ const [isDeleting, setIsDeleting] = useState(false);
+
let content;
if (isLoading) {
@@ -61,44 +68,97 @@ export const DataStreamDetailPanel: React.FunctionComponent = ({
/>
);
} else if (dataStream) {
- content = {JSON.stringify(dataStream)} ;
+ const { timeStampField, generation } = dataStream;
+
+ content = (
+
+
+
+
+
+ {timeStampField.name}
+
+
+
+
+
+ {generation}
+
+ );
}
return (
-
-
-
-
- {dataStreamName}
-
-
-
-
- {content}
-
-
-
-
-
-
-
-
-
-
-
+ <>
+ {isDeleting ? (
+ {
+ if (data && data.hasDeletedDataStreams) {
+ onClose(true);
+ } else {
+ setIsDeleting(false);
+ }
+ }}
+ dataStreams={[dataStreamName]}
+ />
+ ) : null}
+
+
+
+
+
+ {dataStreamName}
+
+
+
+
+ {content}
+
+
+
+
+ onClose()}
+ data-test-subj="closeDetailsButton"
+ >
+
+
+
+
+ {!isLoading && !error ? (
+
+ setIsDeleting(true)}
+ data-test-subj="deleteDataStreamButton"
+ >
+
+
+
+ ) : null}
+
+
+
+ >
);
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
index 951c4a0d7f3c3..bad008b665cfb 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_list.tsx
@@ -12,9 +12,13 @@ import { EuiTitle, EuiText, EuiSpacer, EuiEmptyPrompt, EuiLink } from '@elastic/
import { ScopedHistory } from 'kibana/public';
import { reactRouterNavigate } from '../../../../shared_imports';
+import { useAppContext } from '../../../app_context';
import { SectionError, SectionLoading, Error } from '../../../components';
import { useLoadDataStreams } from '../../../services/api';
+import { decodePathFromReactRouter } from '../../../services/routing';
+import { Section } from '../../home';
import { DataStreamTable } from './data_stream_table';
+import { DataStreamDetailPanel } from './data_stream_detail_panel';
interface MatchParams {
dataStreamName?: string;
@@ -26,6 +30,11 @@ export const DataStreamList: React.FunctionComponent {
+ const {
+ core: { getUrlForApp },
+ plugins: { ingestManager },
+ } = useAppContext();
+
const { error, isLoading, data: dataStreams, sendRequest: reload } = useLoadDataStreams();
let content;
@@ -67,22 +76,52 @@ export const DataStreamList: React.FunctionComponent
- {i18n.translate('xpack.idxMgmt.dataStreamList.emptyPrompt.getStartedLink', {
- defaultMessage: 'composable index template',
- })}
-
- ),
- }}
+ defaultMessage="Data streams represent collections of time series indices."
/>
+ {' ' /* We need this space to separate these two sentences. */}
+ {ingestManager ? (
+
+ {i18n.translate(
+ 'xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIngestManagerLink',
+ {
+ defaultMessage: 'Ingest Manager',
+ }
+ )}
+
+ ),
+ }}
+ />
+ ) : (
+
+ {i18n.translate(
+ 'xpack.idxMgmt.dataStreamList.emptyPrompt.noDataStreamsCtaIndexTemplateLink',
+ {
+ defaultMessage: 'composable index template',
+ }
+ )}
+
+ ),
+ }}
+ />
+ )}
}
data-test-subj="emptyPrompt"
@@ -104,24 +143,38 @@ export const DataStreamList: React.FunctionComponent
-
- {/* TODO: Implement this once we have something to put in here, e.g. storage size, docs count */}
- {/* dataStreamName && (
- {
- history.push('/data_streams');
- }}
- />
- )*/}
>
);
}
- return {content}
;
+ return (
+
+ {content}
+
+ {/*
+ If the user has been deep-linked, they'll expect to see the detail panel because it reflects
+ the URL state, even if there are no data streams or if there was an error loading them.
+ */}
+ {dataStreamName && (
+ {
+ history.push(`/${Section.DataStreams}`);
+
+ // If the data stream was deleted, we need to refresh the list.
+ if (shouldReload) {
+ reload();
+ }
+ }}
+ />
+ )}
+
+ );
};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
index 54035e2193624..d01d8fa03a3fa 100644
--- a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/data_stream_table/data_stream_table.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React from 'react';
+import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiInMemoryTable, EuiBasicTableColumn, EuiButton, EuiLink } from '@elastic/eui';
@@ -13,6 +13,8 @@ import { ScopedHistory } from 'kibana/public';
import { DataStream } from '../../../../../../common/types';
import { reactRouterNavigate } from '../../../../../shared_imports';
import { encodePathForReactRouter } from '../../../../services/routing';
+import { Section } from '../../../home';
+import { DeleteDataStreamConfirmationModal } from '../delete_data_stream_confirmation_modal';
interface Props {
dataStreams?: DataStream[];
@@ -27,6 +29,9 @@ export const DataStreamTable: React.FunctionComponent = ({
history,
filters,
}) => {
+ const [selection, setSelection] = useState([]);
+ const [dataStreamsToDelete, setDataStreamsToDelete] = useState([]);
+
const columns: Array> = [
{
field: 'name',
@@ -35,7 +40,19 @@ export const DataStreamTable: React.FunctionComponent = ({
}),
truncateText: true,
sortable: true,
- // TODO: Render as a link to open the detail panel
+ render: (name: DataStream['name'], item: DataStream) => {
+ return (
+ /* eslint-disable-next-line @elastic/eui/href-or-on-click */
+
+ {name}
+
+ );
+ },
},
{
field: 'indices',
@@ -59,20 +76,27 @@ export const DataStreamTable: React.FunctionComponent = ({
),
},
{
- field: 'timeStampField.name',
- name: i18n.translate('xpack.idxMgmt.dataStreamList.table.timeStampFieldColumnTitle', {
- defaultMessage: 'Timestamp field',
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionColumnTitle', {
+ defaultMessage: 'Actions',
}),
- truncateText: true,
- sortable: true,
- },
- {
- field: 'generation',
- name: i18n.translate('xpack.idxMgmt.dataStreamList.table.generationFieldColumnTitle', {
- defaultMessage: 'Generation',
- }),
- truncateText: true,
- sortable: true,
+ actions: [
+ {
+ name: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteText', {
+ defaultMessage: 'Delete',
+ }),
+ description: i18n.translate('xpack.idxMgmt.dataStreamList.table.actionDeleteDecription', {
+ defaultMessage: 'Delete this data stream',
+ }),
+ icon: 'trash',
+ color: 'danger',
+ type: 'icon',
+ onClick: ({ name }: DataStream) => {
+ setDataStreamsToDelete([name]);
+ },
+ isPrimary: true,
+ 'data-test-subj': 'deleteDataStream',
+ },
+ ],
},
];
@@ -88,12 +112,29 @@ export const DataStreamTable: React.FunctionComponent = ({
},
} as const;
+ const selectionConfig = {
+ onSelectionChange: setSelection,
+ };
+
const searchConfig = {
query: filters,
box: {
incremental: true,
},
- toolsLeft: undefined /* TODO: Actions menu */,
+ toolsLeft:
+ selection.length > 0 ? (
+ setDataStreamsToDelete(selection.map(({ name }: DataStream) => name))}
+ color="danger"
+ >
+
+
+ ) : undefined,
toolsRight: [
= ({
return (
<>
+ {dataStreamsToDelete && dataStreamsToDelete.length > 0 ? (
+ {
+ if (data && data.hasDeletedDataStreams) {
+ reload();
+ } else {
+ setDataStreamsToDelete([]);
+ }
+ }}
+ dataStreams={dataStreamsToDelete}
+ />
+ ) : null}
= ({
search={searchConfig}
sorting={sorting}
isSelectable={true}
+ selection={selectionConfig}
pagination={pagination}
rowProps={() => ({
'data-test-subj': 'row',
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx
new file mode 100644
index 0000000000000..fc8e41aa634b4
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/delete_data_stream_confirmation_modal.tsx
@@ -0,0 +1,149 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { Fragment } from 'react';
+import { EuiCallOut, EuiConfirmModal, EuiOverlayMask, EuiSpacer } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n/react';
+
+import { deleteDataStreams } from '../../../../services/api';
+import { notificationService } from '../../../../services/notification';
+
+interface Props {
+ dataStreams: string[];
+ onClose: (data?: { hasDeletedDataStreams: boolean }) => void;
+}
+
+export const DeleteDataStreamConfirmationModal: React.FunctionComponent = ({
+ dataStreams,
+ onClose,
+}: {
+ dataStreams: string[];
+ onClose: (data?: { hasDeletedDataStreams: boolean }) => void;
+}) => {
+ const dataStreamsCount = dataStreams.length;
+
+ const handleDeleteDataStreams = () => {
+ deleteDataStreams(dataStreams).then(({ data: { dataStreamsDeleted, errors }, error }) => {
+ const hasDeletedDataStreams = dataStreamsDeleted && dataStreamsDeleted.length;
+
+ if (hasDeletedDataStreams) {
+ const successMessage =
+ dataStreamsDeleted.length === 1
+ ? i18n.translate(
+ 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteSingleNotificationMessageText',
+ {
+ defaultMessage: "Deleted data stream '{dataStreamName}'",
+ values: { dataStreamName: dataStreams[0] },
+ }
+ )
+ : i18n.translate(
+ 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.successDeleteMultipleNotificationMessageText',
+ {
+ defaultMessage:
+ 'Deleted {numSuccesses, plural, one {# data stream} other {# data streams}}',
+ values: { numSuccesses: dataStreamsDeleted.length },
+ }
+ );
+
+ onClose({ hasDeletedDataStreams });
+ notificationService.showSuccessToast(successMessage);
+ }
+
+ if (error || (errors && errors.length)) {
+ const hasMultipleErrors =
+ (errors && errors.length > 1) || (error && dataStreams.length > 1);
+
+ const errorMessage = hasMultipleErrors
+ ? i18n.translate(
+ 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.multipleErrorsNotificationMessageText',
+ {
+ defaultMessage: 'Error deleting {count} data streams',
+ values: {
+ count: (errors && errors.length) || dataStreams.length,
+ },
+ }
+ )
+ : i18n.translate(
+ 'xpack.idxMgmt.deleteDataStreamsConfirmationModal.errorNotificationMessageText',
+ {
+ defaultMessage: "Error deleting data stream '{name}'",
+ values: { name: (errors && errors[0].name) || dataStreams[0] },
+ }
+ );
+
+ notificationService.showDangerToast(errorMessage);
+ }
+ });
+ };
+
+ return (
+
+
+ }
+ onCancel={() => onClose()}
+ onConfirm={handleDeleteDataStreams}
+ cancelButtonText={
+
+ }
+ confirmButtonText={
+
+ }
+ >
+
+
+ }
+ color="danger"
+ iconType="alert"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+ {dataStreams.map((name) => (
+ {name}
+ ))}
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/index.ts b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/index.ts
new file mode 100644
index 0000000000000..eaa4a8fc2de02
--- /dev/null
+++ b/x-pack/plugins/index_management/public/application/sections/home/data_stream_list/delete_data_stream_confirmation_modal/index.ts
@@ -0,0 +1,7 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+export { DeleteDataStreamConfirmationModal } from './delete_data_stream_confirmation_modal';
diff --git a/x-pack/plugins/index_management/public/application/services/api.ts b/x-pack/plugins/index_management/public/application/services/api.ts
index 6743e7a7c4403..e831068f03aef 100644
--- a/x-pack/plugins/index_management/public/application/services/api.ts
+++ b/x-pack/plugins/index_management/public/application/services/api.ts
@@ -54,14 +54,21 @@ export function useLoadDataStreams() {
});
}
-// TODO: Implement this API endpoint once we have content to surface in the detail panel.
export function useLoadDataStream(name: string) {
- return useRequest({
- path: `${API_BASE_PATH}/data_stream/${encodeURIComponent(name)}`,
+ return useRequest({
+ path: `${API_BASE_PATH}/data_streams/${encodeURIComponent(name)}`,
method: 'get',
});
}
+export async function deleteDataStreams(dataStreams: string[]) {
+ return sendRequest({
+ path: `${API_BASE_PATH}/delete_data_streams`,
+ method: 'post',
+ body: { dataStreams },
+ });
+}
+
export async function loadIndices() {
const response = await httpService.httpClient.get(`${API_BASE_PATH}/indices`);
return response.data ? response.data : response;
diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts
index 94d9bccdc63ca..aec25ee3247d6 100644
--- a/x-pack/plugins/index_management/public/plugin.ts
+++ b/x-pack/plugins/index_management/public/plugin.ts
@@ -8,6 +8,8 @@ import { i18n } from '@kbn/i18n';
import { CoreSetup } from '../../../../src/core/public';
import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public';
import { ManagementSetup, ManagementSectionId } from '../../../../src/plugins/management/public';
+
+import { IngestManagerSetup } from '../../ingest_manager/public';
import { UIM_APP_NAME, PLUGIN } from '../common/constants';
import { httpService } from './application/services/http';
@@ -25,6 +27,7 @@ export interface IndexManagementPluginSetup {
}
interface PluginsDependencies {
+ ingestManager?: IngestManagerSetup;
usageCollection: UsageCollectionSetup;
management: ManagementSetup;
}
@@ -42,7 +45,7 @@ export class IndexMgmtUIPlugin {
public setup(coreSetup: CoreSetup, plugins: PluginsDependencies): IndexManagementPluginSetup {
const { http, notifications } = coreSetup;
- const { usageCollection, management } = plugins;
+ const { ingestManager, usageCollection, management } = plugins;
httpService.setup(http);
notificationService.setup(notifications);
@@ -60,7 +63,7 @@ export class IndexMgmtUIPlugin {
uiMetricService: this.uiMetricService,
extensionsService: this.extensionsService,
};
- return mountManagementSection(coreSetup, usageCollection, services, params);
+ return mountManagementSection(coreSetup, usageCollection, services, params, ingestManager);
},
});
diff --git a/x-pack/plugins/index_management/server/client/elasticsearch.ts b/x-pack/plugins/index_management/server/client/elasticsearch.ts
index 6b1bf47512b21..6c0fbe3dd6a65 100644
--- a/x-pack/plugins/index_management/server/client/elasticsearch.ts
+++ b/x-pack/plugins/index_management/server/client/elasticsearch.ts
@@ -20,6 +20,20 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any)
method: 'GET',
});
+ dataManagement.getDataStream = ca({
+ urls: [
+ {
+ fmt: '/_data_stream/<%=name%>',
+ req: {
+ name: {
+ type: 'string',
+ },
+ },
+ },
+ ],
+ method: 'GET',
+ });
+
// We don't allow the user to create a data stream in the UI or API. We're just adding this here
// to enable the API integration tests.
dataManagement.createDataStream = ca({
diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
index 56c514e30f242..4aaf2b1bc5ed5 100644
--- a/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/index.ts
@@ -6,8 +6,11 @@
import { RouteDependencies } from '../../../types';
-import { registerGetAllRoute } from './register_get_route';
+import { registerGetOneRoute, registerGetAllRoute } from './register_get_route';
+import { registerDeleteRoute } from './register_delete_route';
export function registerDataStreamRoutes(dependencies: RouteDependencies) {
+ registerGetOneRoute(dependencies);
registerGetAllRoute(dependencies);
+ registerDeleteRoute(dependencies);
}
diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts
new file mode 100644
index 0000000000000..45b185bcd053b
--- /dev/null
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_delete_route.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { schema, TypeOf } from '@kbn/config-schema';
+
+import { RouteDependencies } from '../../../types';
+import { addBasePath } from '../index';
+import { wrapEsError } from '../../helpers';
+
+const bodySchema = schema.object({
+ dataStreams: schema.arrayOf(schema.string()),
+});
+
+export function registerDeleteRoute({ router, license }: RouteDependencies) {
+ router.post(
+ {
+ path: addBasePath('/delete_data_streams'),
+ validate: { body: bodySchema },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+ const { dataStreams } = req.body as TypeOf;
+
+ const response: { dataStreamsDeleted: string[]; errors: any[] } = {
+ dataStreamsDeleted: [],
+ errors: [],
+ };
+
+ await Promise.all(
+ dataStreams.map(async (name: string) => {
+ try {
+ await callAsCurrentUser('dataManagement.deleteDataStream', {
+ name,
+ });
+
+ return response.dataStreamsDeleted.push(name);
+ } catch (e) {
+ return response.errors.push({
+ name,
+ error: wrapEsError(e),
+ });
+ }
+ })
+ );
+
+ return res.ok({ body: response });
+ })
+ );
+}
diff --git a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
index 9128556130bf4..5f4e625348333 100644
--- a/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
+++ b/x-pack/plugins/index_management/server/routes/api/data_streams/register_get_route.ts
@@ -4,7 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { deserializeDataStreamList } from '../../../../common/lib';
+import { schema, TypeOf } from '@kbn/config-schema';
+
+import { deserializeDataStream, deserializeDataStreamList } from '../../../../common/lib';
import { RouteDependencies } from '../../../types';
import { addBasePath } from '../index';
@@ -32,3 +34,40 @@ export function registerGetAllRoute({ router, license, lib: { isEsError } }: Rou
})
);
}
+
+export function registerGetOneRoute({ router, license, lib: { isEsError } }: RouteDependencies) {
+ const paramsSchema = schema.object({
+ name: schema.string(),
+ });
+
+ router.get(
+ {
+ path: addBasePath('/data_streams/{name}'),
+ validate: { params: paramsSchema },
+ },
+ license.guardApiRoute(async (ctx, req, res) => {
+ const { name } = req.params as TypeOf;
+ const { callAsCurrentUser } = ctx.dataManagement!.client;
+
+ try {
+ const dataStream = await callAsCurrentUser('dataManagement.getDataStream', { name });
+
+ if (dataStream[0]) {
+ const body = deserializeDataStream(dataStream[0]);
+ return res.ok({ body });
+ }
+
+ return res.notFound();
+ } catch (e) {
+ if (isEsError(e)) {
+ return res.customError({
+ statusCode: e.statusCode,
+ body: e,
+ });
+ }
+ // Case: default
+ return res.internalError({ body: e });
+ }
+ })
+ );
+}
diff --git a/x-pack/plugins/ingest_manager/public/index.ts b/x-pack/plugins/ingest_manager/public/index.ts
index 9f4893ac6e499..ac56349b30c13 100644
--- a/x-pack/plugins/ingest_manager/public/index.ts
+++ b/x-pack/plugins/ingest_manager/public/index.ts
@@ -6,7 +6,7 @@
import { PluginInitializerContext } from 'src/core/public';
import { IngestManagerPlugin } from './plugin';
-export { IngestManagerStart } from './plugin';
+export { IngestManagerSetup, IngestManagerStart } from './plugin';
export const plugin = (initializerContext: PluginInitializerContext) => {
return new IngestManagerPlugin(initializerContext);
diff --git a/x-pack/plugins/ingest_manager/public/plugin.ts b/x-pack/plugins/ingest_manager/public/plugin.ts
index 3eb2fad339b7d..554df1d406849 100644
--- a/x-pack/plugins/ingest_manager/public/plugin.ts
+++ b/x-pack/plugins/ingest_manager/public/plugin.ts
@@ -22,7 +22,11 @@ import { registerDatasource } from './applications/ingest_manager/sections/agent
export { IngestManagerConfigType } from '../common/types';
-export type IngestManagerSetup = void;
+// We need to provide an object instead of void so that dependent plugins know when Ingest Manager
+// is disabled.
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface IngestManagerSetup {}
+
/**
* Describes public IngestManager plugin contract returned at the `start` stage.
*/
@@ -75,6 +79,8 @@ export class IngestManagerPlugin
};
},
});
+
+ return {};
}
public async start(core: CoreStart): Promise {
From d44144fb8f0e41b87de698d678bef08fe70a60a8 Mon Sep 17 00:00:00 2001
From: Walter Rafelsberger
Date: Thu, 25 Jun 2020 10:55:11 +0200
Subject: [PATCH 21/82] [ML] Data Grid Histograms (#68359) (#69607)
Adds support for histogram charts to data grid columns.
- Adds a toggle button to the data grid's header to enabled/disable column charts.
- When enabled, the charts get rendered as part of the data grid header.
- Histogram charts will get rendered for fields based on date, number, string and boolean.
---
.../ml/common/util/group_color_utils.ts | 16 +-
.../ml/common/util/string_utils.test.ts | 10 +-
x-pack/plugins/ml/common/util/string_utils.ts | 17 +
.../components/data_grid/column_chart.scss | 31 ++
.../components/data_grid/column_chart.tsx | 73 +++
.../components/data_grid/common.ts | 33 +-
.../components/data_grid/data_grid.tsx | 78 +++-
.../application/components/data_grid/index.ts | 2 +
.../application/components/data_grid/types.ts | 12 +-
.../components/data_grid/use_column_chart.tsx | 432 ++++++++++++++++++
.../{use_data_grid.ts => use_data_grid.tsx} | 29 +-
.../data_frame_analytics/common/fields.ts | 2 +-
.../configuration_step_form.tsx | 3 +-
.../hooks/use_index_data.ts | 43 +-
.../exploration_results_table.tsx | 19 +-
.../use_exploration_results.ts | 37 +-
.../outlier_exploration.tsx | 59 +--
.../outlier_exploration/use_outlier_data.ts | 33 +-
.../public/app/hooks/use_index_data.ts | 40 +-
.../public/app/hooks/use_pivot_data.ts | 11 +-
.../expanded_row_preview_pane.tsx | 18 +-
.../transform/public/shared_imports.ts | 3 +
.../apps/transform/creation_index_pattern.ts | 32 +-
.../functional/services/transform/wizard.ts | 58 +++
24 files changed, 996 insertions(+), 95 deletions(-)
create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss
create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx
create mode 100644 x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx
rename x-pack/plugins/ml/public/application/components/data_grid/{use_data_grid.ts => use_data_grid.tsx} (79%)
diff --git a/x-pack/plugins/ml/common/util/group_color_utils.ts b/x-pack/plugins/ml/common/util/group_color_utils.ts
index 92f5c6b2c1347..7105919274185 100644
--- a/x-pack/plugins/ml/common/util/group_color_utils.ts
+++ b/x-pack/plugins/ml/common/util/group_color_utils.ts
@@ -6,6 +6,8 @@
import euiVars from '@elastic/eui/dist/eui_theme_dark.json';
+import { stringHash } from './string_utils';
+
const COLORS = [
euiVars.euiColorVis0,
euiVars.euiColorVis1,
@@ -33,17 +35,3 @@ export function tabColor(name: string): string {
return colorMap[name];
}
}
-
-function stringHash(str: string): number {
- let hash = 0;
- let chr = 0;
- if (str.length === 0) {
- return hash;
- }
- for (let i = 0; i < str.length; i++) {
- chr = str.charCodeAt(i);
- hash = (hash << 5) - hash + chr; // eslint-disable-line no-bitwise
- hash |= 0; // eslint-disable-line no-bitwise
- }
- return hash < 0 ? hash * -2 : hash;
-}
diff --git a/x-pack/plugins/ml/common/util/string_utils.test.ts b/x-pack/plugins/ml/common/util/string_utils.test.ts
index 026c8e6110c99..8afc7e52c9fa5 100644
--- a/x-pack/plugins/ml/common/util/string_utils.test.ts
+++ b/x-pack/plugins/ml/common/util/string_utils.test.ts
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { renderTemplate, getMedianStringLength } from './string_utils';
+import { renderTemplate, getMedianStringLength, stringHash } from './string_utils';
const strings: string[] = [
'foo',
@@ -46,4 +46,12 @@ describe('ML - string utils', () => {
expect(result).toBe(0);
});
});
+
+ describe('stringHash', () => {
+ test('should return a unique number based off a string', () => {
+ const hash1 = stringHash('the-string-1');
+ const hash2 = stringHash('the-string-2');
+ expect(hash1).not.toBe(hash2);
+ });
+ });
});
diff --git a/x-pack/plugins/ml/common/util/string_utils.ts b/x-pack/plugins/ml/common/util/string_utils.ts
index bd4ca02bf93cc..b4591fd2943e6 100644
--- a/x-pack/plugins/ml/common/util/string_utils.ts
+++ b/x-pack/plugins/ml/common/util/string_utils.ts
@@ -22,3 +22,20 @@ export function getMedianStringLength(strings: string[]) {
const sortedStringLengths = strings.map((s) => s.length).sort((a, b) => a - b);
return sortedStringLengths[Math.floor(sortedStringLengths.length / 2)] || 0;
}
+
+/**
+ * Creates a deterministic number based hash out of a string.
+ */
+export function stringHash(str: string): number {
+ let hash = 0;
+ let chr = 0;
+ if (str.length === 0) {
+ return hash;
+ }
+ for (let i = 0; i < str.length; i++) {
+ chr = str.charCodeAt(i);
+ hash = (hash << 5) - hash + chr; // eslint-disable-line no-bitwise
+ hash |= 0; // eslint-disable-line no-bitwise
+ }
+ return hash < 0 ? hash * -2 : hash;
+}
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss
new file mode 100644
index 0000000000000..37d8871ab3562
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.scss
@@ -0,0 +1,31 @@
+.mlDataGridChart__histogram {
+ width: 100%;
+ height: $euiSizeXL + $euiSizeXXL;
+}
+
+.mlDataGridChart__legend {
+ @include euiTextTruncate;
+ @include euiFontSizeXS;
+
+ color: $euiColorMediumShade;
+ display: block;
+ overflow-x: hidden;
+ margin: $euiSizeXS 0px 0px 0px;
+ font-style: italic;
+ font-weight: normal;
+ text-align: left;
+}
+
+.mlDataGridChart__legend--numeric {
+ text-align: right;
+}
+
+.mlDataGridChart__legendBoolean {
+ width: 100%;
+ td { text-align: center }
+}
+
+/* Override to align column header to bottom of cell when no chart is available */
+.mlDataGrid .euiDataGridHeaderCell__content {
+ margin-top: auto;
+}
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/column_chart.tsx
new file mode 100644
index 0000000000000..00e2d5b14a96b
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/components/data_grid/column_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;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import React, { FC } from 'react';
+import classNames from 'classnames';
+
+import { BarSeries, Chart, Settings } from '@elastic/charts';
+import { EuiDataGridColumn } from '@elastic/eui';
+
+import './column_chart.scss';
+
+import { isUnsupportedChartData, useColumnChart, ChartData } from './use_column_chart';
+
+interface Props {
+ chartData: ChartData;
+ columnType: EuiDataGridColumn;
+ dataTestSubj: string;
+}
+
+export const ColumnChart: FC = ({ chartData, columnType, dataTestSubj }) => {
+ const { data, legendText, xScaleType } = useColumnChart(chartData, columnType);
+
+ return (
+
+ {!isUnsupportedChartData(chartData) && data.length > 0 && (
+
+
+
+ d.datum.color}
+ data={data}
+ />
+
+
+ )}
+
+ {legendText}
+
+
{columnType.id}
+
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/common.ts b/x-pack/plugins/ml/public/application/components/data_grid/common.ts
index 7d0559c215114..1f0fcb63f019d 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/common.ts
+++ b/x-pack/plugins/ml/public/application/components/data_grid/common.ts
@@ -13,6 +13,10 @@ import {
EuiDataGridStyle,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+
+import { CoreSetup } from 'src/core/public';
+
import {
IndexPattern,
IFieldType,
@@ -20,6 +24,8 @@ import {
KBN_FIELD_TYPES,
} from '../../../../../../../src/plugins/data/public';
+import { extractErrorMessage } from '../../../../common/util/errors';
+
import {
BASIC_NUMERICAL_TYPES,
EXTENDED_NUMERICAL_TYPES,
@@ -37,7 +43,7 @@ import { mlFieldFormatService } from '../../services/field_format_service';
import { DataGridItem, IndexPagination, RenderCellValue } from './types';
-export const INIT_MAX_COLUMNS = 20;
+export const INIT_MAX_COLUMNS = 10;
export const euiDataGridStyle: EuiDataGridStyle = {
border: 'all',
@@ -102,6 +108,8 @@ export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, results
case 'boolean':
schema = 'boolean';
break;
+ case 'text':
+ schema = NON_AGGREGATABLE;
}
if (
@@ -122,7 +130,10 @@ export const getDataGridSchemasFromFieldTypes = (fieldTypes: FieldTypes, results
});
};
-export const getDataGridSchemaFromKibanaFieldType = (field: IFieldType | undefined) => {
+export const NON_AGGREGATABLE = 'non-aggregatable';
+export const getDataGridSchemaFromKibanaFieldType = (
+ field: IFieldType | undefined
+): string | undefined => {
// Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
// To fall back to the default string schema it needs to be undefined.
let schema;
@@ -143,6 +154,10 @@ export const getDataGridSchemaFromKibanaFieldType = (field: IFieldType | undefin
break;
}
+ if (schema === undefined && field?.aggregatable === false) {
+ return NON_AGGREGATABLE;
+ }
+
return schema;
};
@@ -289,3 +304,17 @@ export const multiColumnSortFactory = (sortingColumns: EuiDataGridSorting['colum
return sortFn;
};
+
+export const showDataGridColumnChartErrorMessageToast = (
+ e: any,
+ toastNotifications: CoreSetup['notifications']['toasts']
+) => {
+ const error = extractErrorMessage(e);
+
+ toastNotifications.addDanger(
+ i18n.translate('xpack.ml.dataGrid.columnChart.ErrorMessageToast', {
+ defaultMessage: 'An error occurred fetching the histogram charts data: {error}',
+ values: { error: error !== '' ? error : e },
+ })
+ );
+};
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
index 618075a77d906..9af7a869e0e56 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
+++ b/x-pack/plugins/ml/public/application/components/data_grid/data_grid.tsx
@@ -10,6 +10,7 @@ import React, { memo, useEffect, FC } from 'react';
import { i18n } from '@kbn/i18n';
import {
+ EuiButtonEmpty,
EuiButtonIcon,
EuiCallOut,
EuiCodeBlock,
@@ -27,6 +28,8 @@ import { INDEX_STATUS } from '../../data_frame_analytics/common';
import { euiDataGridStyle, euiDataGridToolbarSettings } from './common';
import { UseIndexDataReturnType } from './types';
+// TODO Fix row hovering + bar highlighting
+// import { hoveredRow$ } from './column_chart';
export const DataGridTitle: FC<{ title: string }> = ({ title }) => (
@@ -54,7 +57,9 @@ type Props = PropsWithHeader | PropsWithoutHeader;
export const DataGrid: FC = memo(
(props) => {
const {
- columns,
+ chartsVisible,
+ chartsButtonVisible,
+ columnsWithCharts,
dataTestSubj,
errorMessage,
invalidSortingColumnns,
@@ -70,9 +75,18 @@ export const DataGrid: FC = memo(
status,
tableItems: data,
toastNotifications,
+ toggleChartVisibility,
visibleColumns,
} = props;
+ // TODO Fix row hovering + bar highlighting
+ // const getRowProps = (item: any) => {
+ // return {
+ // onMouseOver: () => hoveredRow$.next(item),
+ // onMouseLeave: () => hoveredRow$.next(null),
+ // };
+ // };
+
useEffect(() => {
if (invalidSortingColumnns.length > 0) {
invalidSortingColumnns.forEach((columnId) => {
@@ -162,22 +176,50 @@ export const DataGrid: FC = memo(
)}
-
+
+ {
+ c.initialWidth = 165;
+ return c;
+ })}
+ columnVisibility={{ visibleColumns, setVisibleColumns }}
+ gridStyle={euiDataGridStyle}
+ rowCount={rowCount}
+ renderCellValue={renderCellValue}
+ sorting={{ columns: sortingColumns, onSort }}
+ toolbarVisibility={{
+ ...euiDataGridToolbarSettings,
+ ...(chartsButtonVisible
+ ? {
+ additionalControls: (
+
+ {i18n.translate('xpack.ml.dataGrid.histogramButtonText', {
+ defaultMessage: 'Histogram charts',
+ })}
+
+ ),
+ }
+ : {}),
+ }}
+ pagination={{
+ ...pagination,
+ pageSizeOptions: [5, 10, 25],
+ onChangeItemsPerPage,
+ onChangePage,
+ }}
+ />
+
);
},
@@ -186,7 +228,7 @@ export const DataGrid: FC = memo(
function pickProps(props: Props) {
return [
- props.columns,
+ props.columnsWithCharts,
props.dataTestSubj,
props.errorMessage,
props.invalidSortingColumnns,
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/index.ts b/x-pack/plugins/ml/public/application/components/data_grid/index.ts
index 2472878d1b0c1..80bc6b861f742 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/index.ts
+++ b/x-pack/plugins/ml/public/application/components/data_grid/index.ts
@@ -9,8 +9,10 @@ export {
getDataGridSchemaFromKibanaFieldType,
getFieldsFromKibanaIndexPattern,
multiColumnSortFactory,
+ showDataGridColumnChartErrorMessageToast,
useRenderCellValue,
} from './common';
+export { fetchChartsData, ChartData } from './use_column_chart';
export { useDataGrid } from './use_data_grid';
export { DataGrid } from './data_grid';
export {
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/types.ts b/x-pack/plugins/ml/public/application/components/data_grid/types.ts
index 5fa038edf7815..756f74c8f9302 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/types.ts
+++ b/x-pack/plugins/ml/public/application/components/data_grid/types.ts
@@ -13,6 +13,8 @@ import { Dictionary } from '../../../../common/types/common';
import { INDEX_STATUS } from '../../data_frame_analytics/common/analytics';
+import { ChartData } from './use_column_chart';
+
export type ColumnId = string;
export type DataGridItem = Record;
@@ -54,6 +56,9 @@ export interface SearchResponse7 extends SearchResponse {
export interface UseIndexDataReturnType
extends Pick<
UseDataGridReturnType,
+ | 'chartsVisible'
+ | 'chartsButtonVisible'
+ | 'columnsWithCharts'
| 'errorMessage'
| 'invalidSortingColumnns'
| 'noDataMessage'
@@ -67,13 +72,16 @@ export interface UseIndexDataReturnType
| 'sortingColumns'
| 'status'
| 'tableItems'
+ | 'toggleChartVisibility'
| 'visibleColumns'
> {
- columns: EuiDataGridColumn[];
renderCellValue: RenderCellValue;
}
export interface UseDataGridReturnType {
+ chartsVisible: boolean;
+ chartsButtonVisible: boolean;
+ columnsWithCharts: EuiDataGridColumn[];
errorMessage: string;
invalidSortingColumnns: ColumnId[];
noDataMessage: string;
@@ -83,6 +91,7 @@ export interface UseDataGridReturnType {
pagination: IndexPagination;
resetPagination: () => void;
rowCount: number;
+ setColumnCharts: Dispatch>;
setErrorMessage: Dispatch>;
setNoDataMessage: Dispatch>;
setPagination: Dispatch>;
@@ -94,5 +103,6 @@ export interface UseDataGridReturnType {
sortingColumns: EuiDataGridSorting['columns'];
status: INDEX_STATUS;
tableItems: DataGridItem[];
+ toggleChartVisibility: () => void;
visibleColumns: ColumnId[];
}
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx
new file mode 100644
index 0000000000000..6b207a999eb52
--- /dev/null
+++ b/x-pack/plugins/ml/public/application/components/data_grid/use_column_chart.tsx
@@ -0,0 +1,432 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import moment from 'moment';
+import { BehaviorSubject } from 'rxjs';
+import React from 'react';
+
+import { useObservable } from 'react-use';
+
+import { euiPaletteColorBlind, EuiDataGridColumn } from '@elastic/eui';
+
+import { i18n } from '@kbn/i18n';
+
+import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public';
+
+import { stringHash } from '../../../../common/util/string_utils';
+
+import { NON_AGGREGATABLE } from './common';
+
+export const hoveredRow$ = new BehaviorSubject(null);
+
+const BAR_COLOR = euiPaletteColorBlind()[0];
+const BAR_COLOR_BLUR = euiPaletteColorBlind({ rotations: 2 })[10];
+const MAX_CHART_COLUMNS = 20;
+
+type XScaleType = 'ordinal' | 'time' | 'linear' | undefined;
+const getXScaleType = (kbnFieldType: KBN_FIELD_TYPES | undefined): XScaleType => {
+ switch (kbnFieldType) {
+ case KBN_FIELD_TYPES.BOOLEAN:
+ case KBN_FIELD_TYPES.IP:
+ case KBN_FIELD_TYPES.STRING:
+ return 'ordinal';
+ case KBN_FIELD_TYPES.DATE:
+ return 'time';
+ case KBN_FIELD_TYPES.NUMBER:
+ return 'linear';
+ }
+};
+
+const getFieldType = (schema: EuiDataGridColumn['schema']): KBN_FIELD_TYPES | undefined => {
+ if (schema === NON_AGGREGATABLE) {
+ return undefined;
+ }
+
+ let fieldType: KBN_FIELD_TYPES;
+
+ switch (schema) {
+ case 'datetime':
+ fieldType = KBN_FIELD_TYPES.DATE;
+ break;
+ case 'numeric':
+ fieldType = KBN_FIELD_TYPES.NUMBER;
+ break;
+ case 'boolean':
+ fieldType = KBN_FIELD_TYPES.BOOLEAN;
+ break;
+ case 'json':
+ fieldType = KBN_FIELD_TYPES.OBJECT;
+ break;
+ default:
+ fieldType = KBN_FIELD_TYPES.STRING;
+ }
+
+ return fieldType;
+};
+
+interface NumericColumnStats {
+ interval: number;
+ min: number;
+ max: number;
+}
+type NumericColumnStatsMap = Record;
+const getAggIntervals = async (
+ indexPatternTitle: string,
+ esSearch: (payload: any) => Promise,
+ query: any,
+ columnTypes: EuiDataGridColumn[]
+): Promise => {
+ const numericColumns = columnTypes.filter((cT) => {
+ const fieldType = getFieldType(cT.schema);
+ return fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE;
+ });
+
+ if (numericColumns.length === 0) {
+ return {};
+ }
+
+ const minMaxAggs = numericColumns.reduce((aggs, c) => {
+ const id = stringHash(c.id);
+ aggs[id] = {
+ stats: {
+ field: c.id,
+ },
+ };
+ return aggs;
+ }, {} as Record);
+
+ const respStats = await esSearch({
+ index: indexPatternTitle,
+ size: 0,
+ body: {
+ query,
+ aggs: minMaxAggs,
+ size: 0,
+ },
+ });
+
+ return Object.keys(respStats.aggregations).reduce((p, aggName) => {
+ const stats = [respStats.aggregations[aggName].min, respStats.aggregations[aggName].max];
+ if (!stats.includes(null)) {
+ const delta = respStats.aggregations[aggName].max - respStats.aggregations[aggName].min;
+
+ let aggInterval = 1;
+
+ if (delta > MAX_CHART_COLUMNS) {
+ aggInterval = Math.round(delta / MAX_CHART_COLUMNS);
+ }
+
+ if (delta <= 1) {
+ aggInterval = delta / MAX_CHART_COLUMNS;
+ }
+
+ p[aggName] = { interval: aggInterval, min: stats[0], max: stats[1] };
+ }
+
+ return p;
+ }, {} as NumericColumnStatsMap);
+};
+
+interface AggHistogram {
+ histogram: {
+ field: string;
+ interval: number;
+ };
+}
+
+interface AggCardinality {
+ cardinality: {
+ field: string;
+ };
+}
+
+interface AggTerms {
+ terms: {
+ field: string;
+ size: number;
+ };
+}
+
+type ChartRequestAgg = AggHistogram | AggCardinality | AggTerms;
+
+export const fetchChartsData = async (
+ indexPatternTitle: string,
+ esSearch: (payload: any) => Promise,
+ query: any,
+ columnTypes: EuiDataGridColumn[]
+): Promise => {
+ const aggIntervals = await getAggIntervals(indexPatternTitle, esSearch, query, columnTypes);
+
+ const chartDataAggs = columnTypes.reduce((aggs, c) => {
+ const fieldType = getFieldType(c.schema);
+ const id = stringHash(c.id);
+ if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) {
+ if (aggIntervals[id] !== undefined) {
+ aggs[`${id}_histogram`] = {
+ histogram: {
+ field: c.id,
+ interval: aggIntervals[id].interval !== 0 ? aggIntervals[id].interval : 1,
+ },
+ };
+ }
+ } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) {
+ if (fieldType === KBN_FIELD_TYPES.STRING) {
+ aggs[`${id}_cardinality`] = {
+ cardinality: {
+ field: c.id,
+ },
+ };
+ }
+ aggs[`${id}_terms`] = {
+ terms: {
+ field: c.id,
+ size: MAX_CHART_COLUMNS,
+ },
+ };
+ }
+ return aggs;
+ }, {} as Record);
+
+ if (Object.keys(chartDataAggs).length === 0) {
+ return [];
+ }
+
+ const respChartsData = await esSearch({
+ index: indexPatternTitle,
+ size: 0,
+ body: {
+ query,
+ aggs: chartDataAggs,
+ size: 0,
+ },
+ });
+
+ const chartsData: ChartData[] = columnTypes.map(
+ (c): ChartData => {
+ const fieldType = getFieldType(c.schema);
+ const id = stringHash(c.id);
+
+ if (fieldType === KBN_FIELD_TYPES.NUMBER || fieldType === KBN_FIELD_TYPES.DATE) {
+ if (aggIntervals[id] === undefined) {
+ return {
+ type: 'numeric',
+ data: [],
+ interval: 0,
+ stats: [0, 0],
+ id: c.id,
+ };
+ }
+
+ return {
+ data: respChartsData.aggregations[`${id}_histogram`].buckets,
+ interval: aggIntervals[id].interval,
+ stats: [aggIntervals[id].min, aggIntervals[id].max],
+ type: 'numeric',
+ id: c.id,
+ };
+ } else if (fieldType === KBN_FIELD_TYPES.STRING || fieldType === KBN_FIELD_TYPES.BOOLEAN) {
+ return {
+ type: fieldType === KBN_FIELD_TYPES.STRING ? 'ordinal' : 'boolean',
+ cardinality:
+ fieldType === KBN_FIELD_TYPES.STRING
+ ? respChartsData.aggregations[`${id}_cardinality`].value
+ : 2,
+ data: respChartsData.aggregations[`${id}_terms`].buckets,
+ id: c.id,
+ };
+ }
+
+ return {
+ type: 'unsupported',
+ id: c.id,
+ };
+ }
+ );
+
+ return chartsData;
+};
+
+interface NumericDataItem {
+ key: number;
+ key_as_string?: string;
+ doc_count: number;
+}
+
+interface NumericChartData {
+ data: NumericDataItem[];
+ id: string;
+ interval: number;
+ stats: [number, number];
+ type: 'numeric';
+}
+
+export const isNumericChartData = (arg: any): arg is NumericChartData => {
+ return (
+ arg.hasOwnProperty('data') &&
+ arg.hasOwnProperty('id') &&
+ arg.hasOwnProperty('interval') &&
+ arg.hasOwnProperty('stats') &&
+ arg.hasOwnProperty('type')
+ );
+};
+
+interface OrdinalDataItem {
+ key: string;
+ key_as_string?: string;
+ doc_count: number;
+}
+
+interface OrdinalChartData {
+ type: 'ordinal' | 'boolean';
+ cardinality: number;
+ data: OrdinalDataItem[];
+ id: string;
+}
+
+export const isOrdinalChartData = (arg: any): arg is OrdinalChartData => {
+ return (
+ arg.hasOwnProperty('data') &&
+ arg.hasOwnProperty('cardinality') &&
+ arg.hasOwnProperty('id') &&
+ arg.hasOwnProperty('type')
+ );
+};
+
+interface UnsupportedChartData {
+ id: string;
+ type: 'unsupported';
+}
+
+export const isUnsupportedChartData = (arg: any): arg is UnsupportedChartData => {
+ return arg.hasOwnProperty('type') && arg.type === 'unsupported';
+};
+
+type ChartDataItem = NumericDataItem | OrdinalDataItem;
+export type ChartData = NumericChartData | OrdinalChartData | UnsupportedChartData;
+
+type LegendText = string | JSX.Element;
+const getLegendText = (chartData: ChartData): LegendText => {
+ if (chartData.type === 'unsupported') {
+ return i18n.translate('xpack.ml.dataGridChart.histogramNotAvailable', {
+ defaultMessage: 'Chart not supported.',
+ });
+ }
+
+ if (chartData.data.length === 0) {
+ return i18n.translate('xpack.ml.dataGridChart.notEnoughData', {
+ defaultMessage: `0 documents contain field.`,
+ });
+ }
+
+ if (chartData.type === 'boolean') {
+ return (
+
+
+
+ {chartData.data[0] !== undefined && {chartData.data[0].key_as_string} }
+ {chartData.data[1] !== undefined && {chartData.data[1].key_as_string} }
+
+
+
+ );
+ }
+
+ if (isOrdinalChartData(chartData) && chartData.cardinality <= MAX_CHART_COLUMNS) {
+ return i18n.translate('xpack.ml.dataGridChart.singleCategoryLegend', {
+ defaultMessage: `{cardinality, plural, one {# category} other {# categories}}`,
+ values: { cardinality: chartData.cardinality },
+ });
+ }
+
+ if (isOrdinalChartData(chartData) && chartData.cardinality > MAX_CHART_COLUMNS) {
+ return i18n.translate('xpack.ml.dataGridChart.topCategoriesLegend', {
+ defaultMessage: `top {MAX_CHART_COLUMNS} of {cardinality} categories`,
+ values: { cardinality: chartData.cardinality, MAX_CHART_COLUMNS },
+ });
+ }
+
+ if (isNumericChartData(chartData)) {
+ const fromValue = Math.round(chartData.stats[0] * 100) / 100;
+ const toValue = Math.round(chartData.stats[1] * 100) / 100;
+
+ return fromValue !== toValue ? `${fromValue} - ${toValue}` : '' + fromValue;
+ }
+
+ return '';
+};
+
+interface ColumnChart {
+ data: ChartDataItem[];
+ legendText: LegendText;
+ xScaleType: XScaleType;
+}
+
+export const useColumnChart = (
+ chartData: ChartData,
+ columnType: EuiDataGridColumn
+): ColumnChart => {
+ const fieldType = getFieldType(columnType.schema);
+
+ const hoveredRow = useObservable(hoveredRow$);
+
+ const xScaleType = getXScaleType(fieldType);
+
+ const getColor = (d: ChartDataItem) => {
+ if (hoveredRow === undefined || hoveredRow === null) {
+ return BAR_COLOR;
+ }
+
+ if (
+ isOrdinalChartData(chartData) &&
+ xScaleType === 'ordinal' &&
+ hoveredRow._source[columnType.id] === d.key
+ ) {
+ return BAR_COLOR;
+ }
+
+ if (
+ isNumericChartData(chartData) &&
+ xScaleType === 'linear' &&
+ hoveredRow._source[columnType.id] >= +d.key &&
+ hoveredRow._source[columnType.id] < +d.key + chartData.interval
+ ) {
+ return BAR_COLOR;
+ }
+
+ if (
+ isNumericChartData(chartData) &&
+ xScaleType === 'time' &&
+ moment(hoveredRow._source[columnType.id]).unix() * 1000 >= +d.key &&
+ moment(hoveredRow._source[columnType.id]).unix() * 1000 < +d.key + chartData.interval
+ ) {
+ return BAR_COLOR;
+ }
+
+ return BAR_COLOR_BLUR;
+ };
+
+ let data: ChartDataItem[] = [];
+
+ // The if/else if/else is a work-around because `.map()` doesn't work with union types.
+ // See TS Caveats for details: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-3.html#caveats
+ if (isOrdinalChartData(chartData)) {
+ data = chartData.data.map((d: OrdinalDataItem) => ({
+ ...d,
+ color: getColor(d),
+ }));
+ } else if (isNumericChartData(chartData)) {
+ data = chartData.data.map((d: NumericDataItem) => ({
+ ...d,
+ color: getColor(d),
+ }));
+ }
+
+ return {
+ data,
+ legendText: getLegendText(chartData),
+ xScaleType,
+ };
+};
diff --git a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx
similarity index 79%
rename from x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts
rename to x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx
index 7843bf2ea801b..47c84ce6cd664 100644
--- a/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.ts
+++ b/x-pack/plugins/ml/public/application/components/data_grid/use_data_grid.tsx
@@ -4,12 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
import { EuiDataGridSorting, EuiDataGridColumn } from '@elastic/eui';
import { INDEX_STATUS } from '../../data_frame_analytics/common';
+import { ColumnChart } from './column_chart';
import { INIT_MAX_COLUMNS } from './common';
import {
ColumnId,
@@ -20,6 +21,7 @@ import {
OnSort,
UseDataGridReturnType,
} from './types';
+import { ChartData } from './use_column_chart';
export const useDataGrid = (
columns: EuiDataGridColumn[],
@@ -33,9 +35,15 @@ export const useDataGrid = (
const [errorMessage, setErrorMessage] = useState('');
const [status, setStatus] = useState(INDEX_STATUS.UNUSED);
const [rowCount, setRowCount] = useState(0);
+ const [columnCharts, setColumnCharts] = useState([]);
const [tableItems, setTableItems] = useState([]);
const [pagination, setPagination] = useState(defaultPagination);
const [sortingColumns, setSortingColumns] = useState([]);
+ const [chartsVisible, setChartsVisible] = useState(false);
+
+ const toggleChartVisibility = () => {
+ setChartsVisible(!chartsVisible);
+ };
const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback((pageSize) => {
setPagination((p) => {
@@ -87,6 +95,23 @@ export const useDataGrid = (
);
return {
+ chartsVisible,
+ chartsButtonVisible: true,
+ columnsWithCharts: columns.map((c, index) => {
+ const chartData = columnCharts.find((cd) => cd.id === c.id);
+
+ return {
+ ...c,
+ display:
+ chartData !== undefined && chartsVisible === true ? (
+
+ ) : undefined,
+ };
+ }),
errorMessage,
invalidSortingColumnns,
noDataMessage,
@@ -96,6 +121,7 @@ export const useDataGrid = (
pagination,
resetPagination,
rowCount,
+ setColumnCharts,
setErrorMessage,
setNoDataMessage,
setPagination,
@@ -107,6 +133,7 @@ export const useDataGrid = (
sortingColumns,
status,
tableItems,
+ toggleChartVisibility,
visibleColumns,
};
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
index 0a64886c80a63..1b28875a624f8 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/common/fields.ts
@@ -30,7 +30,7 @@ export interface EsDoc extends Record {
_source: EsDocSource;
}
-export const MAX_COLUMNS = 20;
+export const MAX_COLUMNS = 10;
export const DEFAULT_REGRESSION_COLUMNS = 8;
export const BASIC_NUMERICAL_TYPES = new Set([
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
index e63756686a4ba..2c3079e24cf1d 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/components/configuration_step/configuration_step_form.tsx
@@ -87,7 +87,8 @@ export const ConfigurationStepForm: FC = ({
const indexData = useIndexData(
currentIndexPattern,
- savedSearchQuery !== undefined ? savedSearchQuery : jobConfigQuery
+ savedSearchQuery !== undefined ? savedSearchQuery : jobConfigQuery,
+ toastNotifications
);
const indexPreviewProps = {
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts
index e8f25584201e3..ee0e5c1955ead 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_creation/hooks/use_index_data.ts
@@ -6,10 +6,16 @@
import { useEffect } from 'react';
+import { EuiDataGridColumn } from '@elastic/eui';
+
+import { CoreSetup } from 'src/core/public';
+
import { IndexPattern } from '../../../../../../../../../src/plugins/data/public';
import {
+ fetchChartsData,
getDataGridSchemaFromKibanaFieldType,
getFieldsFromKibanaIndexPattern,
+ showDataGridColumnChartErrorMessageToast,
useDataGrid,
useRenderCellValue,
EsSorting,
@@ -22,15 +28,19 @@ import { ml } from '../../../../services/ml_api_service';
type IndexSearchResponse = SearchResponse7;
-export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDataReturnType => {
+export const useIndexData = (
+ indexPattern: IndexPattern,
+ query: any,
+ toastNotifications: CoreSetup['notifications']['toasts']
+): UseIndexDataReturnType => {
const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern);
// EuiDataGrid State
- const columns = [
+ const columns: EuiDataGridColumn[] = [
...indexPatternFields.map((id) => {
const field = indexPattern.fields.getByName(id);
const schema = getDataGridSchemaFromKibanaFieldType(field);
- return { id, schema };
+ return { id, schema, isExpandable: schema !== 'boolean' };
}),
];
@@ -93,11 +103,36 @@ export const useIndexData = (indexPattern: IndexPattern, query: any): UseIndexDa
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]);
+ const fetchColumnChartsData = async function () {
+ try {
+ const columnChartsData = await fetchChartsData(
+ indexPattern.title,
+ ml.esSearch,
+ query,
+ columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id))
+ );
+ dataGrid.setColumnCharts(columnChartsData);
+ } catch (e) {
+ showDataGridColumnChartErrorMessageToast(e, toastNotifications);
+ }
+ };
+
+ useEffect(() => {
+ if (dataGrid.chartsVisible) {
+ fetchColumnChartsData();
+ }
+ // custom comparison
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ dataGrid.chartsVisible,
+ indexPattern.title,
+ JSON.stringify([query, dataGrid.visibleColumns]),
+ ]);
+
const renderCellValue = useRenderCellValue(indexPattern, pagination, tableItems);
return {
...dataGrid,
- columns,
renderCellValue,
};
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx
index 105eb9f73804d..941fbefd78084 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/exploration_results_table.tsx
@@ -67,9 +67,20 @@ export const ExplorationResultsTable: FC = React.memo(
setEvaluateSearchQuery(searchQuery);
}, [JSON.stringify(searchQuery)]);
- const classificationData = useExplorationResults(indexPattern, jobConfig, searchQuery);
- const docFieldsCount = classificationData.columns.length;
- const { columns, errorMessage, status, tableItems, visibleColumns } = classificationData;
+ const classificationData = useExplorationResults(
+ indexPattern,
+ jobConfig,
+ searchQuery,
+ getToastNotifications()
+ );
+ const docFieldsCount = classificationData.columnsWithCharts.length;
+ const {
+ columnsWithCharts,
+ errorMessage,
+ status,
+ tableItems,
+ visibleColumns,
+ } = classificationData;
if (jobConfig === undefined || classificationData === undefined) {
return null;
@@ -140,7 +151,7 @@ export const ExplorationResultsTable: FC = React.memo(
- {(columns.length > 0 || searchQuery !== defaultSearchQuery) && (
+ {(columnsWithCharts.length > 0 || searchQuery !== defaultSearchQuery) && (
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts
index b8b5a16c84e85..796670f6a864d 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration_results_table/use_exploration_results.ts
@@ -8,15 +8,20 @@ import { useEffect } from 'react';
import { EuiDataGridColumn } from '@elastic/eui';
+import { CoreSetup } from 'src/core/public';
+
import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public';
import {
+ fetchChartsData,
getDataGridSchemasFromFieldTypes,
+ showDataGridColumnChartErrorMessageToast,
useDataGrid,
useRenderCellValue,
UseIndexDataReturnType,
} from '../../../../../components/data_grid';
import { SavedSearchQuery } from '../../../../../contexts/ml';
+import { ml } from '../../../../../services/ml_api_service';
import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common';
import {
@@ -29,7 +34,8 @@ import { sortExplorationResultsFields, ML__ID_COPY } from '../../../../common/fi
export const useExplorationResults = (
indexPattern: IndexPattern | undefined,
jobConfig: DataFrameAnalyticsConfig | undefined,
- searchQuery: SavedSearchQuery
+ searchQuery: SavedSearchQuery,
+ toastNotifications: CoreSetup['notifications']['toasts']
): UseIndexDataReturnType => {
const needsDestIndexFields =
indexPattern !== undefined && indexPattern.title === jobConfig?.source.index[0];
@@ -66,6 +72,34 @@ export const useExplorationResults = (
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]);
+ const fetchColumnChartsData = async function () {
+ try {
+ if (jobConfig !== undefined) {
+ const columnChartsData = await fetchChartsData(
+ jobConfig.dest.index,
+ ml.esSearch,
+ searchQuery,
+ columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id))
+ );
+ dataGrid.setColumnCharts(columnChartsData);
+ }
+ } catch (e) {
+ showDataGridColumnChartErrorMessageToast(e, toastNotifications);
+ }
+ };
+
+ useEffect(() => {
+ if (dataGrid.chartsVisible) {
+ fetchColumnChartsData();
+ }
+ // custom comparison
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ dataGrid.chartsVisible,
+ jobConfig?.dest.index,
+ JSON.stringify([searchQuery, dataGrid.visibleColumns]),
+ ]);
+
const renderCellValue = useRenderCellValue(
indexPattern,
dataGrid.pagination,
@@ -75,7 +109,6 @@ export const useExplorationResults = (
return {
...dataGrid,
- columns,
renderCellValue,
};
};
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx
index 917ab1b0ed1dd..0b29b7f43bfc8 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/outlier_exploration.tsx
@@ -53,7 +53,7 @@ export const OutlierExploration: FC = React.memo(({ jobId }) =
const [searchQuery, setSearchQuery] = useState(defaultSearchQuery);
const outlierData = useOutlierData(indexPattern, jobConfig, searchQuery);
- const { columns, errorMessage, status, tableItems } = outlierData;
+ const { columnsWithCharts, errorMessage, status, tableItems } = outlierData;
// if it's a searchBar syntax error leave the table visible so they can try again
if (status === INDEX_STATUS.ERROR && !errorMessage.includes('failed to create query')) {
@@ -98,35 +98,36 @@ export const OutlierExploration: FC = React.memo(({ jobId }) =
)}
- {(columns.length > 0 || searchQuery !== defaultSearchQuery) && indexPattern !== undefined && (
- <>
-
-
-
-
-
-
- 0 || searchQuery !== defaultSearchQuery) &&
+ indexPattern !== undefined && (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {columnsWithCharts.length > 0 && tableItems.length > 0 && (
+
-
-
-
- {columns.length > 0 && tableItems.length > 0 && (
-
- )}
- >
- )}
+ )}
+ >
+ )}
);
});
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts
index 37ab67e2a33cb..beb6836bf801f 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/outlier_exploration/use_outlier_data.ts
@@ -16,12 +16,16 @@ import {
COLOR_RANGE_SCALE,
} from '../../../../../components/color_range_legend';
import {
+ fetchChartsData,
getDataGridSchemasFromFieldTypes,
+ showDataGridColumnChartErrorMessageToast,
useDataGrid,
useRenderCellValue,
UseIndexDataReturnType,
} from '../../../../../components/data_grid';
import { SavedSearchQuery } from '../../../../../contexts/ml';
+import { ml } from '../../../../../services/ml_api_service';
+import { getToastNotifications } from '../../../../../util/dependency_cache';
import { getIndexData, getIndexFields, DataFrameAnalyticsConfig } from '../../../../common';
import { DEFAULT_RESULTS_FIELD, FEATURE_INFLUENCE } from '../../../../common/constants';
@@ -75,6 +79,34 @@ export const useOutlierData = (
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [jobConfig && jobConfig.id, dataGrid.pagination, searchQuery, dataGrid.sortingColumns]);
+ const fetchColumnChartsData = async function () {
+ try {
+ if (jobConfig !== undefined) {
+ const columnChartsData = await fetchChartsData(
+ jobConfig.dest.index,
+ ml.esSearch,
+ searchQuery,
+ columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id))
+ );
+ dataGrid.setColumnCharts(columnChartsData);
+ }
+ } catch (e) {
+ showDataGridColumnChartErrorMessageToast(e, getToastNotifications());
+ }
+ };
+
+ useEffect(() => {
+ if (dataGrid.chartsVisible) {
+ fetchColumnChartsData();
+ }
+ // custom comparison
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ dataGrid.chartsVisible,
+ jobConfig?.dest.index,
+ JSON.stringify([searchQuery, dataGrid.visibleColumns]),
+ ]);
+
const colorRange = useColorRange(
COLOR_RANGE.BLUE,
COLOR_RANGE_SCALE.INFLUENCER,
@@ -115,7 +147,6 @@ export const useOutlierData = (
return {
...dataGrid,
- columns,
renderCellValue,
};
};
diff --git a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts
index e1aa27df5e979..c821c183ad370 100644
--- a/x-pack/plugins/transform/public/app/hooks/use_index_data.ts
+++ b/x-pack/plugins/transform/public/app/hooks/use_index_data.ts
@@ -6,10 +6,14 @@
import { useEffect } from 'react';
+import { EuiDataGridColumn } from '@elastic/eui';
+
import {
+ fetchChartsData,
getDataGridSchemaFromKibanaFieldType,
getFieldsFromKibanaIndexPattern,
getErrorMessage,
+ showDataGridColumnChartErrorMessageToast,
useDataGrid,
useRenderCellValue,
EsSorting,
@@ -23,6 +27,8 @@ import { isDefaultQuery, matchAllQuery, PivotQuery } from '../common';
import { SearchItems } from './use_search_items';
import { useApi } from './use_api';
+import { useToastNotifications } from '../app_dependencies';
+
type IndexSearchResponse = SearchResponse7;
export const useIndexData = (
@@ -30,11 +36,12 @@ export const useIndexData = (
query: PivotQuery
): UseIndexDataReturnType => {
const api = useApi();
+ const toastNotifications = useToastNotifications();
const indexPatternFields = getFieldsFromKibanaIndexPattern(indexPattern);
// EuiDataGrid State
- const columns = [
+ const columns: EuiDataGridColumn[] = [
...indexPatternFields.map((id) => {
const field = indexPattern.fields.getByName(id);
const schema = getDataGridSchemaFromKibanaFieldType(field);
@@ -45,8 +52,10 @@ export const useIndexData = (
const dataGrid = useDataGrid(columns);
const {
+ chartsVisible,
pagination,
resetPagination,
+ setColumnCharts,
setErrorMessage,
setRowCount,
setStatus,
@@ -61,7 +70,7 @@ export const useIndexData = (
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(query)]);
- const getIndexData = async function () {
+ const fetchDataGridData = async function () {
setErrorMessage('');
setStatus(INDEX_STATUS.LOADING);
@@ -92,20 +101,43 @@ export const useIndexData = (
} catch (e) {
setErrorMessage(getErrorMessage(e));
setStatus(INDEX_STATUS.ERROR);
+ return;
+ }
+ };
+
+ const fetchColumnChartsData = async function () {
+ try {
+ const columnChartsData = await fetchChartsData(
+ indexPattern.title,
+ api.esSearch,
+ isDefaultQuery(query) ? matchAllQuery : query,
+ columns.filter((cT) => dataGrid.visibleColumns.includes(cT.id))
+ );
+
+ setColumnCharts(columnChartsData);
+ } catch (e) {
+ showDataGridColumnChartErrorMessageToast(e, toastNotifications);
}
};
useEffect(() => {
- getIndexData();
+ fetchDataGridData();
// custom comparison
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [indexPattern.title, JSON.stringify([query, pagination, sortingColumns])]);
+ useEffect(() => {
+ if (chartsVisible) {
+ fetchColumnChartsData();
+ }
+ // custom comparison
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [chartsVisible, indexPattern.title, JSON.stringify([query, dataGrid.visibleColumns])]);
+
const renderCellValue = useRenderCellValue(indexPattern, pagination, tableItems);
return {
...dataGrid,
- columns,
renderCellValue,
};
};
diff --git a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts
index 206dab173297b..a9f34996b9b51 100644
--- a/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts
+++ b/x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts
@@ -7,6 +7,8 @@
import moment from 'moment-timezone';
import { useEffect, useMemo, useState } from 'react';
+import { EuiDataGridColumn } from '@elastic/eui';
+
import { i18n } from '@kbn/i18n';
import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/common';
@@ -76,7 +78,7 @@ export const usePivotData = (
const api = useApi();
const aggsArr = useMemo(() => dictionaryToArray(aggs), [aggs]);
- const groupByArr = dictionaryToArray(groupBy);
+ const groupByArr = useMemo(() => dictionaryToArray(groupBy), [groupBy]);
// Filters mapping properties of type `object`, which get returned for nested field parents.
const columnKeys = Object.keys(previewMappings.properties).filter(
@@ -85,7 +87,7 @@ export const usePivotData = (
columnKeys.sort(sortColumns(groupByArr));
// EuiDataGrid State
- const columns = columnKeys.map((id) => {
+ const columns: EuiDataGridColumn[] = columnKeys.map((id) => {
const field = previewMappings.properties[id];
// Built-in values are ['boolean', 'currency', 'datetime', 'numeric', 'json']
@@ -195,8 +197,7 @@ export const usePivotData = (
}, [
indexPatternTitle,
aggsArr,
- JSON.stringify(groupByArr),
- JSON.stringify(query),
+ JSON.stringify([groupByArr, query]),
/* eslint-enable react-hooks/exhaustive-deps */
]);
@@ -251,7 +252,7 @@ export const usePivotData = (
return {
...dataGrid,
- columns,
+ chartsButtonVisible: false,
renderCellValue,
};
};
diff --git a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx
index e183712b390cf..a917fc73ad8fb 100644
--- a/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx
+++ b/x-pack/plugins/transform/public/app/sections/transform_management/components/transform_list/expanded_row_preview_pane.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { FC } from 'react';
+import React, { useMemo, FC } from 'react';
import { DataGrid } from '../../../../../shared_imports';
@@ -24,14 +24,22 @@ interface ExpandedRowPreviewPaneProps {
export const ExpandedRowPreviewPane: FC = ({ transformConfig }) => {
const toastNotifications = useToastNotifications();
- const { aggList, groupByList, searchQuery } = applyTransformConfigToDefineState(
- getDefaultStepDefineState({} as SearchItems),
- transformConfig
+
+ const { aggList, groupByList, searchQuery } = useMemo(
+ () =>
+ applyTransformConfigToDefineState(
+ getDefaultStepDefineState({} as SearchItems),
+ transformConfig
+ ),
+ [transformConfig]
);
- const pivotQuery = getPivotQuery(searchQuery);
+
+ const pivotQuery = useMemo(() => getPivotQuery(searchQuery), [searchQuery]);
+
const indexPatternTitle = Array.isArray(transformConfig.source.index)
? transformConfig.source.index.join(',')
: transformConfig.source.index;
+
const pivotPreviewProps = usePivotData(indexPatternTitle, pivotQuery, aggList, groupByList);
return (
diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts
index b6650f08f12bd..e0bbcd0b5d9db 100644
--- a/x-pack/plugins/transform/public/shared_imports.ts
+++ b/x-pack/plugins/transform/public/shared_imports.ts
@@ -14,14 +14,17 @@ export {
} from '../../../../src/plugins/es_ui_shared/public';
export {
+ fetchChartsData,
getErrorMessage,
extractErrorMessage,
formatHumanReadableDateTimeSeconds,
getDataGridSchemaFromKibanaFieldType,
getFieldsFromKibanaIndexPattern,
multiColumnSortFactory,
+ showDataGridColumnChartErrorMessageToast,
useDataGrid,
useRenderCellValue,
+ ChartData,
DataGrid,
EsSorting,
RenderCellValue,
diff --git a/x-pack/test/functional/apps/transform/creation_index_pattern.ts b/x-pack/test/functional/apps/transform/creation_index_pattern.ts
index 76ce5e953e104..bf267c80cdcce 100644
--- a/x-pack/test/functional/apps/transform/creation_index_pattern.ts
+++ b/x-pack/test/functional/apps/transform/creation_index_pattern.ts
@@ -147,9 +147,25 @@ export default function ({ getService }: FtrProviderContext) {
progress: '100',
},
indexPreview: {
- columns: 20,
+ columns: 10,
rows: 5,
},
+ histogramCharts: [
+ { chartAvailable: false, id: 'category', legend: 'Chart not supported.' },
+ { chartAvailable: true, id: 'currency', legend: '1 category' },
+ {
+ chartAvailable: false,
+ id: 'customer_birth_date',
+ legend: '0 documents contain field.',
+ },
+ { chartAvailable: false, id: 'customer_first_name', legend: 'Chart not supported.' },
+ { chartAvailable: false, id: 'customer_full_name', legend: 'Chart not supported.' },
+ { chartAvailable: true, id: 'customer_gender', legend: '2 categories' },
+ { chartAvailable: true, id: 'customer_id', legend: 'top 20 of 46 categories' },
+ { chartAvailable: false, id: 'customer_last_name', legend: 'Chart not supported.' },
+ { chartAvailable: true, id: 'customer_phone', legend: '1 category' },
+ { chartAvailable: true, id: 'day_of_week', legend: '7 categories' },
+ ],
},
},
{
@@ -229,9 +245,10 @@ export default function ({ getService }: FtrProviderContext) {
progress: '100',
},
indexPreview: {
- columns: 20,
+ columns: 10,
rows: 5,
},
+ histogramCharts: [],
},
},
];
@@ -289,6 +306,16 @@ export default function ({ getService }: FtrProviderContext) {
await transform.wizard.assertAdvancedQueryEditorSwitchCheckState(false);
});
+ it('enables the index preview histogram charts', async () => {
+ await transform.wizard.enableIndexPreviewHistogramCharts();
+ });
+
+ it('displays the index preview histogram charts', async () => {
+ await transform.wizard.assertIndexPreviewHistogramCharts(
+ testData.expected.histogramCharts
+ );
+ });
+
it('adds the group by entries', async () => {
for (const [index, entry] of testData.groupByEntries.entries()) {
await transform.wizard.assertGroupByInputExists();
@@ -323,6 +350,7 @@ export default function ({ getService }: FtrProviderContext) {
});
it('shows the pivot preview', async () => {
+ await transform.wizard.assertPivotPreviewChartHistogramButtonMissing();
await transform.wizard.assertPivotPreviewColumnValues(
testData.expected.pivotPreview.column,
testData.expected.pivotPreview.values
diff --git a/x-pack/test/functional/services/transform/wizard.ts b/x-pack/test/functional/services/transform/wizard.ts
index 8b61e8c895e30..9cfdbadac8a3b 100644
--- a/x-pack/test/functional/services/transform/wizard.ts
+++ b/x-pack/test/functional/services/transform/wizard.ts
@@ -76,6 +76,12 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) {
await testSubjects.existOrFail(selector);
},
+ async assertPivotPreviewChartHistogramButtonMissing() {
+ // the button should not exist because histogram charts
+ // for the pivot preview are not supported yet
+ await testSubjects.missingOrFail('transformPivotPreviewHistogramButton');
+ },
+
async parseEuiDataGrid(tableSubj: string) {
const table = await testSubjects.find(`~${tableSubj}`);
const $ = await table.parseDomContent();
@@ -155,6 +161,58 @@ export function TransformWizardProvider({ getService }: FtrProviderContext) {
await this.assertPivotPreviewExists('empty');
},
+ async assertIndexPreviewHistogramChartButtonExists() {
+ await testSubjects.existOrFail('transformIndexPreviewHistogramButton');
+ },
+
+ async enableIndexPreviewHistogramCharts() {
+ await this.assertIndexPreviewHistogramChartButtonCheckState(false);
+ await testSubjects.click('transformIndexPreviewHistogramButton');
+ await this.assertIndexPreviewHistogramChartButtonCheckState(true);
+ },
+
+ async assertIndexPreviewHistogramChartButtonCheckState(expectedCheckState: boolean) {
+ const actualCheckState =
+ (await testSubjects.getAttribute(
+ 'transformIndexPreviewHistogramButton',
+ 'aria-checked'
+ )) === 'true';
+ expect(actualCheckState).to.eql(
+ expectedCheckState,
+ `Chart histogram button check state should be '${expectedCheckState}' (got '${actualCheckState}')`
+ );
+ },
+
+ async assertIndexPreviewHistogramCharts(
+ expectedHistogramCharts: Array<{ chartAvailable: boolean; id: string; legend: string }>
+ ) {
+ // For each chart, get the content of each header cell and assert
+ // the legend text and column id and if the chart should be present or not.
+ await retry.tryForTime(5000, async () => {
+ for (const [index, expected] of expectedHistogramCharts.entries()) {
+ await testSubjects.existOrFail(`mlDataGridChart-${index}`);
+
+ if (expected.chartAvailable) {
+ await testSubjects.existOrFail(`mlDataGridChart-${index}-histogram`);
+ } else {
+ await testSubjects.missingOrFail(`mlDataGridChart-${index}-histogram`);
+ }
+
+ const actualLegend = await testSubjects.getVisibleText(`mlDataGridChart-${index}-legend`);
+ expect(actualLegend).to.eql(
+ expected.legend,
+ `Legend text for column '${index}' should be '${expected.legend}' (got '${actualLegend}')`
+ );
+
+ const actualId = await testSubjects.getVisibleText(`mlDataGridChart-${index}-id`);
+ expect(actualId).to.eql(
+ expected.id,
+ `Id text for column '${index}' should be '${expected.id}' (got '${actualId}')`
+ );
+ }
+ });
+ },
+
async assertQueryInputExists() {
await testSubjects.existOrFail('transformQueryInput');
},
From bf4557321bfc0c5629d9456be70b6fd610d34b1e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?=
Date: Thu, 25 Jun 2020 11:28:50 +0200
Subject: [PATCH 22/82] [7.x][DOCS] Emphasizes where File Data Visualizer is
located. (#69895)
---
docs/images/data-viz-homepage.jpg | Bin 0 -> 180960 bytes
docs/setup/connect-to-elasticsearch.asciidoc | 14 ++++++++++----
2 files changed, 10 insertions(+), 4 deletions(-)
create mode 100644 docs/images/data-viz-homepage.jpg
diff --git a/docs/images/data-viz-homepage.jpg b/docs/images/data-viz-homepage.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f7a952b65d41f430d75187c56924f397ae1e5cbd
GIT binary patch
literal 180960
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