diff --git a/.ci/Jenkinsfile_flaky b/.ci/Jenkinsfile_flaky index 3f77243da2c1b..e1cbac0528b1f 100644 --- a/.ci/Jenkinsfile_flaky +++ b/.ci/Jenkinsfile_flaky @@ -8,26 +8,28 @@ def JOB_PARTS = params.CI_GROUP.split(':') def IS_XPACK = JOB_PARTS[0] == 'xpack' def JOB = JOB_PARTS[1] def CI_GROUP = JOB_PARTS.size() > 2 ? JOB_PARTS[2] : '' +def EXECUTIONS = params.NUMBER_EXECUTIONS.toInteger() +def AGENT_COUNT = getAgentCount(EXECUTIONS) def worker = getWorkerFromParams(IS_XPACK, JOB, CI_GROUP) def workerFailures = [] currentBuild.displayName += trunc(" ${params.GITHUB_OWNER}:${params.branch_specifier}", 24) -currentBuild.description = "${params.CI_GROUP}
Executions: ${params.NUMBER_EXECUTIONS}" - -// Note: If you increase agent count, it will execute NUMBER_EXECUTIONS per agent. It will not divide them up amongst the agents -// e.g. NUMBER_EXECUTIONS = 25, agentCount = 4 results in 100 total executions -def agentCount = 1 +currentBuild.description = "${params.CI_GROUP}
Agents: ${AGENT_COUNT}
Executions: ${params.NUMBER_EXECUTIONS}" stage("Kibana Pipeline") { timeout(time: 180, unit: 'MINUTES') { timestamps { ansiColor('xterm') { def agents = [:] - for(def agentNumber = 1; agentNumber <= agentCount; agentNumber++) { + for(def agentNumber = 1; agentNumber <= AGENT_COUNT; agentNumber++) { + def agentNumberInside = agentNumber + def agentExecutions = floor(EXECUTIONS/AGENT_COUNT) + (agentNumber <= EXECUTIONS%AGENT_COUNT ? 1 : 0) agents["agent-${agentNumber}"] = { catchError { + print "Agent ${agentNumberInside} - ${agentExecutions} executions" + kibanaPipeline.withWorkers('flaky-test-runner', { if (!IS_XPACK) { kibanaPipeline.buildOss() @@ -37,7 +39,7 @@ stage("Kibana Pipeline") { } else { kibanaPipeline.buildXpack() } - }, getWorkerMap(agentNumber, params.NUMBER_EXECUTIONS.toInteger(), worker, workerFailures))() + }, getWorkerMap(agentNumberInside, agentExecutions, worker, workerFailures))() } } } @@ -77,9 +79,9 @@ def getWorkerFromParams(isXpack, job, ciGroup) { } } -def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWorkers = 14) { +def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWorkerProcesses = 12) { def workerMap = [:] - def numberOfWorkers = Math.min(numberOfExecutions, maxWorkers) + def numberOfWorkers = Math.min(numberOfExecutions, maxWorkerProcesses) for(def i = 1; i <= numberOfWorkers; i++) { def workerExecutions = numberOfExecutions/numberOfWorkers + (i <= numberOfExecutions%numberOfWorkers ? 1 : 0) @@ -87,7 +89,10 @@ def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWor workerMap["agent-${agentNumber}-worker-${i}"] = { workerNumber -> for(def j = 0; j < workerExecutions; j++) { print "Execute agent-${agentNumber} worker-${workerNumber}: ${j}" - withEnv(["JOB=agent-${agentNumber}-worker-${workerNumber}-${j}"]) { + withEnv([ + "JOB=agent-${agentNumber}-worker-${workerNumber}-${j}", + "REMOVE_KIBANA_INSTALL_DIR=1", + ]) { catchError { try { worker(workerNumber) @@ -104,6 +109,11 @@ def getWorkerMap(agentNumber, numberOfExecutions, worker, workerFailures, maxWor return workerMap } +def getAgentCount(executions) { + // Increase agent count every 24 worker processess, up to 3 agents maximum + return Math.min(3, 1 + floor(executions/24)) +} + def trunc(str, length) { if (str.size() >= length) { return str.take(length) + "..." @@ -111,3 +121,11 @@ def trunc(str, length) { return str; } + +// All of the real rounding/truncating methods are sandboxed +def floor(num) { + return num + .toString() + .split('\\.')[0] + .toInteger() +} diff --git a/.ci/jobs.yml b/.ci/jobs.yml index fc3e80064fa6f..89fce3cf488d5 100644 --- a/.ci/jobs.yml +++ b/.ci/jobs.yml @@ -14,7 +14,7 @@ JOB: - kibana-ciGroup10 - kibana-ciGroup11 - kibana-ciGroup12 - # - kibana-visualRegression + - kibana-visualRegression # make sure all x-pack-ciGroups are listed in test/scripts/jenkins_xpack_ci_group.sh - x-pack-firefoxSmoke @@ -28,7 +28,7 @@ JOB: - x-pack-ciGroup8 - x-pack-ciGroup9 - x-pack-ciGroup10 - # - x-pack-visualRegression + - x-pack-visualRegression # `~` is yaml for `null` exclude: ~ diff --git a/.eslintrc.js b/.eslintrc.js index a7b712dabb6c2..310ee47819e30 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -206,8 +206,6 @@ module.exports = { files: ['x-pack/legacy/plugins/ml/**/*.{js,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', - 'react-hooks/rules-of-hooks': 'off', - 'jsx-a11y/click-events-have-key-events': 'off', }, }, { @@ -229,12 +227,6 @@ module.exports = { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['x-pack/legacy/plugins/transform/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, { files: ['x-pack/legacy/plugins/uptime/**/*.{js,ts,tsx}'], rules: { @@ -370,10 +362,10 @@ module.exports = { '!src/core/server/*.test.mocks.ts', 'src/plugins/**/public/**/*', - '!src/plugins/**/public/index*', + '!src/plugins/**/public/index.{js,ts,tsx}', 'src/plugins/**/server/**/*', - '!src/plugins/**/server/index*', + '!src/plugins/**/server/index.{js,ts,tsx}', ], allowSameFolder: true, }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7001e32754abb..267691fde8197 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,9 +8,14 @@ # App Architecture /src/plugins/data/ @elastic/kibana-app-arch -/src/plugins/kibana_utils/ @elastic/kibana-app-arch +/src/plugins/embeddable/ @elastic/kibana-app-arch +/src/plugins/expressions/ @elastic/kibana-app-arch /src/plugins/kibana_react/ @elastic/kibana-app-arch +/src/plugins/kibana_utils/ @elastic/kibana-app-arch /src/plugins/navigation/ @elastic/kibana-app-arch +/src/plugins/ui_actions/ @elastic/kibana-app-arch +/src/plugins/visualizations/ @elastic/kibana-app-arch +/x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch # APM /x-pack/legacy/plugins/apm/ @elastic/apm-ui @@ -37,7 +42,9 @@ /x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/ml.ts @elastic/ml-ui -/x-pack/legacy/plugins/transform/ @elastic/ml-ui +# ML team owns the transform plugin, ES team added here for visibility +# because the plugin lives in Kibana's Elasticsearch management section. +/x-pack/legacy/plugins/transform/ @elastic/ml-ui @elastic/es-ui # Operations /renovate.json5 @elastic/kibana-operations @@ -56,6 +63,7 @@ /src/legacy/server/saved_objects/ @elastic/kibana-platform /config/kibana.yml @elastic/kibana-platform /x-pack/plugins/features/ @elastic/kibana-platform +/x-pack/plugins/licensing/ @elastic/kibana-platform # Security /x-pack/legacy/plugins/security/ @elastic/kibana-security @@ -88,9 +96,6 @@ /x-pack/legacy/plugins/rollup/ @elastic/es-ui /x-pack/legacy/plugins/searchprofiler/ @elastic/es-ui /x-pack/legacy/plugins/snapshot_restore/ @elastic/es-ui -# ML team owns the transform plugin, ES team added here for visibility -# because the plugin lives in Kibana's Elasticsearch management section. -/x-pack/legacy/plugins/transform/ @elastic/es-ui /x-pack/legacy/plugins/watcher/ @elastic/es-ui # Kibana TSVB external contractors diff --git a/.i18nrc.json b/.i18nrc.json index d77293e3dc8f7..908cf192e7988 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -3,6 +3,7 @@ "common.ui": "src/legacy/ui", "data": ["src/legacy/core_plugins/data", "src/plugins/data"], "expressions": "src/legacy/core_plugins/expressions", + "expressions_np": "src/plugins/expressions", "kibana_react": "src/legacy/core_plugins/kibana_react", "navigation": "src/legacy/core_plugins/navigation", "server": "src/legacy/server", @@ -29,6 +30,7 @@ "kbnESQuery": "packages/kbn-es-query", "inspector": "src/plugins/inspector", "kibana-react": "src/plugins/kibana_react", + "visualizations": "src/plugins/visualizations", "telemetry": "src/legacy/core_plugins/telemetry", "esUi": "src/plugins/es_ui_shared", "uiActions": "src/plugins/ui_actions" diff --git a/Jenkinsfile b/Jenkinsfile index 4820de9876737..12600788ccbcb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -25,7 +25,7 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a 'oss-ciGroup11': kibanaPipeline.getOssCiGroupWorker(11), 'oss-ciGroup12': kibanaPipeline.getOssCiGroupWorker(12), 'oss-firefoxSmoke': kibanaPipeline.getPostBuildWorker('firefoxSmoke', { runbld './test/scripts/jenkins_firefox_smoke.sh' }), - // 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }), + 'oss-visualRegression': kibanaPipeline.getPostBuildWorker('visualRegression', { runbld './test/scripts/jenkins_visual_regression.sh' }), ]), 'kibana-xpack-agent': kibanaPipeline.withWorkers('kibana-xpack-tests', { kibanaPipeline.buildXpack() }, [ 'xpack-ciGroup1': kibanaPipeline.getXpackCiGroupWorker(1), @@ -39,7 +39,7 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a 'xpack-ciGroup9': kibanaPipeline.getXpackCiGroupWorker(9), 'xpack-ciGroup10': kibanaPipeline.getXpackCiGroupWorker(10), 'xpack-firefoxSmoke': kibanaPipeline.getPostBuildWorker('xpack-firefoxSmoke', { runbld './test/scripts/jenkins_xpack_firefox_smoke.sh' }), - // 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }), + 'xpack-visualRegression': kibanaPipeline.getPostBuildWorker('xpack-visualRegression', { runbld './test/scripts/jenkins_xpack_visual_regression.sh' }), ]), ]) } @@ -47,4 +47,4 @@ stage("Kibana Pipeline") { // This stage is just here to help the BlueOcean UI a } } } -} +} \ No newline at end of file diff --git a/docs/discover/kuery.asciidoc b/docs/discover/kuery.asciidoc index a87b8d5a78604..abf3e05fb7819 100644 --- a/docs/discover/kuery.asciidoc +++ b/docs/discover/kuery.asciidoc @@ -73,3 +73,87 @@ set these terms will be matched against all fields. For example, a query for `re in the response field, but a query for just `200` will search for 200 across all fields in your index. ============ +===== Nested Field Support + +KQL supports querying on {ref}/nested.html[nested fields] through a special syntax. You can query nested fields in subtly different +ways, depending on the results you want, so crafting nested queries requires extra thought. + +One main consideration is how to match parts of the nested query to the individual nested documents. +There are two main approaches to take: + +* *Parts of the query may only match a single nested document.* This is what most users want when querying on a nested field. +* *Parts of the query can match different nested documents.* This is how a regular object field works. + Although generally less useful, there might be occasions where you want to query a nested field in this way. + +Let's take a look at the first approach. In the following document, `items` is a nested field: + +[source,json] +---------------------------------- +{ + "grocery_name": "Elastic Eats", + "items": [ + { + "name": "banana", + "stock": "12", + "category": "fruit" + }, + { + "name": "peach", + "stock": "10", + "category": "fruit" + }, + { + "name": "carrot", + "stock": "9", + "category": "vegetable" + }, + { + "name": "broccoli", + "stock": "5", + "category": "vegetable" + } + ] +} +---------------------------------- + +To find stores that have more than 10 bananas in stock, you would write a query like this: + +`items:{ name:banana and stock > 10 }` + +`items` is the "nested path". Everything inside the curly braces (the "nested group") must match a single document. +For example, `items:{ name:banana and stock:9 }` does not match because there isn't a single nested document that +matches the entire query in the nested group. + +What if you want to find a store with more than 10 bananas that *also* stocks vegetables? This is the second way of querying a nested field, and you can do it like this: + +`items:{ name:banana and stock > 10 } and items:{ category:vegetable }` + +The first nested group (`name:banana and stock > 10`) must still match a single document, but the `category:vegetables` +subquery can match a different nested document because it is in a separate group. + +KQL's syntax also supports nested fields inside of other nested fields—you simply have to specify the full path. Suppose you +have a document where `level1` and `level2` are both nested fields: + +[source,json] +---------------------------------- +{ + "level1": [ + { + "level2": [ + { + "prop1": "foo", + "prop2": "bar" + }, + { + "prop1": "baz", + "prop2": "qux" + } + ] + } + ] +} +---------------------------------- + +You can match on a single nested document by specifying the full path: + +`level1.level2:{ prop1:foo and prop2:bar }` diff --git a/docs/uptime-guide/security.asciidoc b/docs/uptime-guide/security.asciidoc index 41efcc25627be..2a960348b1e02 100644 --- a/docs/uptime-guide/security.asciidoc +++ b/docs/uptime-guide/security.asciidoc @@ -31,17 +31,6 @@ PUT /_security/role/uptime "allow_restricted_indices" : false } ], - "applications" : [ - { - "application" : "kibana-.kibana", - "privileges" : [ - "all" - ], - "resources" : [ - "*" - ] - } - ], "transient_metadata" : { "enabled" : true } @@ -52,7 +41,8 @@ PUT /_security/role/uptime [float] === Assign the role to a user -Next, you'll need to create a user with both the `kibana_user`, and `uptime` roles. +Next, you'll need to create a user with both the `uptime` role, and another role with sufficient {kibana-ref}/kibana-privileges.html[Kibana privileges], +such as the `kibana_user` role. You can do this with the following request: ["source","sh",subs="attributes,callouts"] diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 82dd88cb57495..4a5ca41ae6be9 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -32,8 +32,8 @@ for different operating systems. . Open {kib} in your web browser and log in. If you are running {kib} locally, go to `http://localhost:5601`. To access {kib} and generate -reports, you need the `kibana_user` and `reporting_user` roles. For more -information, see <>. +reports, you need the `reporting_user` role, and an additional role with succifient <>, such as the `kibana_user` role. +For more information, see <>. . Open the dashboard, visualization, or saved search you want to include in the report. diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index 8a4678e051490..c2b1adc5e1b92 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -138,7 +138,7 @@ xpack.security.authc.saml.maxRedirectURLSize: 1kb ==== OpenID Connect Single Sign-On Similar to SAML, authentication with OpenID Connect allows users to log in to {kib} using an OpenID Connect Provider such as Google, or Okta. OpenID Connect -should also be configured in {es}, see {xpack-ref}/saml-guide.html[Configuring OpenID Connect Single-Sign-On on the Elastic Stack] for more details. +should also be configured in {es}. For more details, see {ref}/oidc-guide.html[Configuring single sign-on to the {stack} using OpenID Connect]. Set the configuration values in `kibana.yml` as follows: diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc index ffbef3bdc9b18..1d7a3f4978ee0 100644 --- a/docs/user/security/reporting.asciidoc +++ b/docs/user/security/reporting.asciidoc @@ -19,7 +19,7 @@ and `kibana_user` roles: * If you're using the `native` realm, you can assign roles through **Management / Users** UI in Kibana or with the `user` API. For example, the following request creates a `reporter` user that has the -`reporting_user` and `kibana_user` roles: +`reporting_user` role, and another role with sufficient <>, such as the `kibana_user` role: + [source, sh] --------------------------------------------------------------- diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index fa11d5925bdbe..1c74bd98642a7 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -117,7 +117,7 @@ user you've assigned a {kib} user role. For example, you could log in as the + -- -NOTE: This must be a user who has been assigned the `kibana_user` role. +NOTE: This must be a user who has been assigned <>. {kib} server credentials should only be used internally by the {kib} server. -- diff --git a/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json b/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json index 408e839596ae2..588e6ada69cfe 100644 --- a/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json +++ b/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json @@ -161,8 +161,7 @@ "searchable": true, "aggregatable": true, "readFromDocValues": true, - "parent": "machine.os", - "subType": "multi" + "subType": { "multi": { "parent": "machine.os" } } }, { "name": "geo.src", @@ -277,6 +276,28 @@ "searchable": true, "aggregatable": true, "readFromDocValues": false + }, + { + "name": "nestedField.child", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false, + "subType": { "nested": { "path": "nestedField" } } + }, + { + "name": "nestedField.nestedChild.doublyNestedChild", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false, + "subType": { "nested": { "path": "nestedField.nestedChild" } } } ] } diff --git a/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js b/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js index 1ba10ddca54ac..3cbe1203bc533 100644 --- a/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js +++ b/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js @@ -198,6 +198,68 @@ describe('kuery AST API', function () { expect(actual).to.eql(expected); }); + it('should support nested queries indicated by curly braces', () => { + const expected = nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'foo') + ); + const actual = ast.fromKueryExpression('nestedField:{ childOfNested: foo }'); + expect(actual).to.eql(expected); + }); + + it('should support nested subqueries and subqueries inside nested queries', () => { + const expected = nodeTypes.function.buildNode( + 'and', + [ + nodeTypes.function.buildNode('is', 'response', '200'), + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', 'childOfNested', 'foo'), + nodeTypes.function.buildNode('is', 'childOfNested', 'bar'), + ]) + )]); + const actual = ast.fromKueryExpression('response:200 and nestedField:{ childOfNested:foo or childOfNested:bar }'); + expect(actual).to.eql(expected); + }); + + it('should support nested sub-queries inside paren groups', () => { + const expected = nodeTypes.function.buildNode( + 'and', + [ + nodeTypes.function.buildNode('is', 'response', '200'), + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'foo') + ), + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'bar') + ), + ]) + ]); + const actual = ast.fromKueryExpression('response:200 and ( nestedField:{ childOfNested:foo } or nestedField:{ childOfNested:bar } )'); + expect(actual).to.eql(expected); + }); + + it('should support nested groups inside other nested groups', () => { + const expected = nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode( + 'nested', + 'nestedChild', + nodeTypes.function.buildNode('is', 'doublyNestedChild', 'foo') + ) + ); + const actual = ast.fromKueryExpression('nestedField:{ nestedChild:{ doublyNestedChild:foo } }'); + expect(actual).to.eql(expected); + }); }); describe('fromLiteralExpression', function () { diff --git a/packages/kbn-es-query/src/kuery/ast/ast.js b/packages/kbn-es-query/src/kuery/ast/ast.js index 0bf2b258ba1f4..1688995d46f80 100644 --- a/packages/kbn-es-query/src/kuery/ast/ast.js +++ b/packages/kbn-es-query/src/kuery/ast/ast.js @@ -63,12 +63,12 @@ function fromExpression(expression, parseOptions = {}, parse = parseKuery) { * IndexPattern isn't required, but if you pass one in, we can be more intelligent * about how we craft the queries (e.g. scripted fields) */ -export function toElasticsearchQuery(node, indexPattern, config = {}) { +export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) { if (!node || !node.type || !nodeTypes[node.type]) { return toElasticsearchQuery(nodeTypes.function.buildNode('and', [])); } - return nodeTypes[node.type].toElasticsearchQuery(node, indexPattern, config); + return nodeTypes[node.type].toElasticsearchQuery(node, indexPattern, config, context); } export function doesKueryExpressionHaveLuceneSyntaxError(expression) { diff --git a/packages/kbn-es-query/src/kuery/ast/kuery.js b/packages/kbn-es-query/src/kuery/ast/kuery.js index d39145df79073..f745f01873bae 100644 --- a/packages/kbn-es-query/src/kuery/ast/kuery.js +++ b/packages/kbn-es-query/src/kuery/ast/kuery.js @@ -74,8 +74,30 @@ module.exports = (function() { } return query; }, - peg$c10 = { type: "other", description: "fieldName" }, - peg$c11 = function(field, operator, value) { + peg$c10 = ":", + peg$c11 = { type: "literal", value: ":", description: "\":\"" }, + peg$c12 = "{", + peg$c13 = { type: "literal", value: "{", description: "\"{\"" }, + peg$c14 = "}", + peg$c15 = { type: "literal", value: "}", description: "\"}\"" }, + peg$c16 = function(field, query, trailing) { + if (query.type === 'cursor') { + return { + ...query, + nestedPath: query.nestedPath ? `${field.value}.${query.nestedPath}` : field.value, + } + }; + + if (trailing.type === 'cursor') { + return { + ...trailing, + suggestionTypes: ['conjunction'] + }; + } + return buildFunctionNode('nested', [field, query]); + }, + peg$c17 = { type: "other", description: "fieldName" }, + peg$c18 = function(field, operator, value) { if (value.type === 'cursor') { return { ...value, @@ -85,9 +107,7 @@ module.exports = (function() { const range = buildNamedArgNode(operator, value); return buildFunctionNode('range', [field, range]); }, - peg$c12 = ":", - peg$c13 = { type: "literal", value: ":", description: "\":\"" }, - peg$c14 = function(field, partial) { + peg$c19 = function(field, partial) { if (partial.type === 'cursor') { return { ...partial, @@ -97,7 +117,7 @@ module.exports = (function() { } return partial(field); }, - peg$c15 = function(partial) { + peg$c20 = function(partial) { if (partial.type === 'cursor') { const fieldName = `${partial.prefix}${partial.suffix}`.trim(); return { @@ -109,7 +129,7 @@ module.exports = (function() { const field = buildLiteralNode(null); return partial(field); }, - peg$c16 = function(partial, trailing) { + peg$c21 = function(partial, trailing) { if (trailing.type === 'cursor') { return { ...trailing, @@ -118,7 +138,7 @@ module.exports = (function() { } return partial; }, - peg$c17 = function(partialLeft, partialRight) { + peg$c22 = function(partialLeft, partialRight) { const cursor = [partialLeft, partialRight].find(node => node.type === 'cursor'); if (cursor) { return { @@ -128,7 +148,7 @@ module.exports = (function() { } return (field) => buildFunctionNode('or', [partialLeft(field), partialRight(field)]); }, - peg$c18 = function(partialLeft, partialRight) { + peg$c23 = function(partialLeft, partialRight) { const cursor = [partialLeft, partialRight].find(node => node.type === 'cursor'); if (cursor) { return { @@ -138,7 +158,7 @@ module.exports = (function() { } return (field) => buildFunctionNode('and', [partialLeft(field), partialRight(field)]); }, - peg$c19 = function(partial) { + peg$c24 = function(partial) { if (partial.type === 'cursor') { return { ...list, @@ -147,13 +167,13 @@ module.exports = (function() { } return (field) => buildFunctionNode('not', [partial(field)]); }, - peg$c20 = { type: "other", description: "value" }, - peg$c21 = function(value) { + peg$c25 = { type: "other", description: "value" }, + peg$c26 = function(value) { if (value.type === 'cursor') return value; const isPhrase = buildLiteralNode(true); return (field) => buildFunctionNode('is', [field, value, isPhrase]); }, - peg$c22 = function(value) { + peg$c27 = function(value) { if (value.type === 'cursor') return value; if (!allowLeadingWildcards && value.type === 'wildcard' && nodeTypes.wildcard.hasLeadingWildcard(value)) { @@ -163,19 +183,19 @@ module.exports = (function() { const isPhrase = buildLiteralNode(false); return (field) => buildFunctionNode('is', [field, value, isPhrase]); }, - peg$c23 = { type: "other", description: "OR" }, - peg$c24 = "or", - peg$c25 = { type: "literal", value: "or", description: "\"or\"" }, - peg$c26 = { type: "other", description: "AND" }, - peg$c27 = "and", - peg$c28 = { type: "literal", value: "and", description: "\"and\"" }, - peg$c29 = { type: "other", description: "NOT" }, - peg$c30 = "not", - peg$c31 = { type: "literal", value: "not", description: "\"not\"" }, - peg$c32 = { type: "other", description: "literal" }, - peg$c33 = "\"", - peg$c34 = { type: "literal", value: "\"", description: "\"\\\"\"" }, - peg$c35 = function(prefix, cursor, suffix) { + peg$c28 = { type: "other", description: "OR" }, + peg$c29 = "or", + peg$c30 = { type: "literal", value: "or", description: "\"or\"" }, + peg$c31 = { type: "other", description: "AND" }, + peg$c32 = "and", + peg$c33 = { type: "literal", value: "and", description: "\"and\"" }, + peg$c34 = { type: "other", description: "NOT" }, + peg$c35 = "not", + peg$c36 = { type: "literal", value: "not", description: "\"not\"" }, + peg$c37 = { type: "other", description: "literal" }, + peg$c38 = "\"", + peg$c39 = { type: "literal", value: "\"", description: "\"\\\"\"" }, + peg$c40 = function(prefix, cursor, suffix) { const { start, end } = location(); return { type: 'cursor', @@ -186,17 +206,17 @@ module.exports = (function() { text: text().replace(cursor, '') }; }, - peg$c36 = function(chars) { + peg$c41 = function(chars) { return buildLiteralNode(chars.join('')); }, - peg$c37 = "\\", - peg$c38 = { type: "literal", value: "\\", description: "\"\\\\\"" }, - peg$c39 = /^[\\"]/, - peg$c40 = { type: "class", value: "[\\\\\"]", description: "[\\\\\"]" }, - peg$c41 = function(char) { return char; }, - peg$c42 = /^[^"]/, - peg$c43 = { type: "class", value: "[^\"]", description: "[^\"]" }, - peg$c44 = function(chars) { + peg$c42 = "\\", + peg$c43 = { type: "literal", value: "\\", description: "\"\\\\\"" }, + peg$c44 = /^[\\"]/, + peg$c45 = { type: "class", value: "[\\\\\"]", description: "[\\\\\"]" }, + peg$c46 = function(char) { return char; }, + peg$c47 = /^[^"]/, + peg$c48 = { type: "class", value: "[^\"]", description: "[^\"]" }, + peg$c49 = function(chars) { const sequence = chars.join('').trim(); if (sequence === 'null') return buildLiteralNode(null); if (sequence === 'true') return buildLiteralNode(true); @@ -206,108 +226,104 @@ module.exports = (function() { const value = isNaN(number) ? sequence : number; return buildLiteralNode(value); }, - peg$c45 = { type: "any", description: "any character" }, - peg$c46 = "*", - peg$c47 = { type: "literal", value: "*", description: "\"*\"" }, - peg$c48 = function() { return wildcardSymbol; }, - peg$c49 = "\\t", - peg$c50 = { type: "literal", value: "\\t", description: "\"\\\\t\"" }, - peg$c51 = function() { return '\t'; }, - peg$c52 = "\\r", - peg$c53 = { type: "literal", value: "\\r", description: "\"\\\\r\"" }, - peg$c54 = function() { return '\r'; }, - peg$c55 = "\\n", - peg$c56 = { type: "literal", value: "\\n", description: "\"\\\\n\"" }, - peg$c57 = function() { return '\n'; }, - peg$c58 = function(keyword) { return keyword; }, - peg$c59 = /^[\\():<>"*]/, - peg$c60 = { type: "class", value: "[\\\\():<>\"*]", description: "[\\\\():<>\"*]" }, - peg$c61 = "<=", - peg$c62 = { type: "literal", value: "<=", description: "\"<=\"" }, - peg$c63 = function() { return 'lte'; }, - peg$c64 = ">=", - peg$c65 = { type: "literal", value: ">=", description: "\">=\"" }, - peg$c66 = function() { return 'gte'; }, - peg$c67 = "<", - peg$c68 = { type: "literal", value: "<", description: "\"<\"" }, - peg$c69 = function() { return 'lt'; }, - peg$c70 = ">", - peg$c71 = { type: "literal", value: ">", description: "\">\"" }, - peg$c72 = function() { return 'gt'; }, - peg$c73 = { type: "other", description: "whitespace" }, - peg$c74 = /^[ \t\r\n]/, - peg$c75 = { type: "class", value: "[\\ \\t\\r\\n]", description: "[\\ \\t\\r\\n]" }, - peg$c76 = function() { return parseCursor; }, - peg$c77 = "@kuery-cursor@", - peg$c78 = { type: "literal", value: "@kuery-cursor@", description: "\"@kuery-cursor@\"" }, - peg$c79 = function() { return cursorSymbol; }, - peg$c80 = "||", - peg$c81 = { type: "literal", value: "||", description: "\"||\"" }, - peg$c82 = function() { + peg$c50 = { type: "any", description: "any character" }, + peg$c51 = "*", + peg$c52 = { type: "literal", value: "*", description: "\"*\"" }, + peg$c53 = function() { return wildcardSymbol; }, + peg$c54 = "\\t", + peg$c55 = { type: "literal", value: "\\t", description: "\"\\\\t\"" }, + peg$c56 = function() { return '\t'; }, + peg$c57 = "\\r", + peg$c58 = { type: "literal", value: "\\r", description: "\"\\\\r\"" }, + peg$c59 = function() { return '\r'; }, + peg$c60 = "\\n", + peg$c61 = { type: "literal", value: "\\n", description: "\"\\\\n\"" }, + peg$c62 = function() { return '\n'; }, + peg$c63 = function(keyword) { return keyword; }, + peg$c64 = /^[\\():<>"*{}]/, + peg$c65 = { type: "class", value: "[\\\\():<>\"*{}]", description: "[\\\\():<>\"*{}]" }, + peg$c66 = "<=", + peg$c67 = { type: "literal", value: "<=", description: "\"<=\"" }, + peg$c68 = function() { return 'lte'; }, + peg$c69 = ">=", + peg$c70 = { type: "literal", value: ">=", description: "\">=\"" }, + peg$c71 = function() { return 'gte'; }, + peg$c72 = "<", + peg$c73 = { type: "literal", value: "<", description: "\"<\"" }, + peg$c74 = function() { return 'lt'; }, + peg$c75 = ">", + peg$c76 = { type: "literal", value: ">", description: "\">\"" }, + peg$c77 = function() { return 'gt'; }, + peg$c78 = { type: "other", description: "whitespace" }, + peg$c79 = /^[ \t\r\n]/, + peg$c80 = { type: "class", value: "[\\ \\t\\r\\n]", description: "[\\ \\t\\r\\n]" }, + peg$c81 = function() { return parseCursor; }, + peg$c82 = "@kuery-cursor@", + peg$c83 = { type: "literal", value: "@kuery-cursor@", description: "\"@kuery-cursor@\"" }, + peg$c84 = function() { return cursorSymbol; }, + peg$c85 = "||", + peg$c86 = { type: "literal", value: "||", description: "\"||\"" }, + peg$c87 = function() { error('LuceneOr'); }, - peg$c83 = "&&", - peg$c84 = { type: "literal", value: "&&", description: "\"&&\"" }, - peg$c85 = function() { + peg$c88 = "&&", + peg$c89 = { type: "literal", value: "&&", description: "\"&&\"" }, + peg$c90 = function() { error('LuceneAnd'); }, - peg$c86 = "+", - peg$c87 = { type: "literal", value: "+", description: "\"+\"" }, - peg$c88 = "-", - peg$c89 = { type: "literal", value: "-", description: "\"-\"" }, - peg$c90 = function() { + peg$c91 = "+", + peg$c92 = { type: "literal", value: "+", description: "\"+\"" }, + peg$c93 = "-", + peg$c94 = { type: "literal", value: "-", description: "\"-\"" }, + peg$c95 = function() { error('LuceneNot'); }, - peg$c91 = "!", - peg$c92 = { type: "literal", value: "!", description: "\"!\"" }, - peg$c93 = "_exists_", - peg$c94 = { type: "literal", value: "_exists_", description: "\"_exists_\"" }, - peg$c95 = function() { + peg$c96 = "!", + peg$c97 = { type: "literal", value: "!", description: "\"!\"" }, + peg$c98 = "_exists_", + peg$c99 = { type: "literal", value: "_exists_", description: "\"_exists_\"" }, + peg$c100 = function() { error('LuceneExists'); }, - peg$c96 = function() { + peg$c101 = function() { error('LuceneRange'); }, - peg$c97 = "?", - peg$c98 = { type: "literal", value: "?", description: "\"?\"" }, - peg$c99 = function() { + peg$c102 = "?", + peg$c103 = { type: "literal", value: "?", description: "\"?\"" }, + peg$c104 = function() { error('LuceneWildcard'); }, - peg$c100 = "/", - peg$c101 = { type: "literal", value: "/", description: "\"/\"" }, - peg$c102 = /^[^\/]/, - peg$c103 = { type: "class", value: "[^/]", description: "[^/]" }, - peg$c104 = function() { + peg$c105 = "/", + peg$c106 = { type: "literal", value: "/", description: "\"/\"" }, + peg$c107 = /^[^\/]/, + peg$c108 = { type: "class", value: "[^/]", description: "[^/]" }, + peg$c109 = function() { error('LuceneRegex'); }, - peg$c105 = "~", - peg$c106 = { type: "literal", value: "~", description: "\"~\"" }, - peg$c107 = /^[0-9]/, - peg$c108 = { type: "class", value: "[0-9]", description: "[0-9]" }, - peg$c109 = function() { + peg$c110 = "~", + peg$c111 = { type: "literal", value: "~", description: "\"~\"" }, + peg$c112 = /^[0-9]/, + peg$c113 = { type: "class", value: "[0-9]", description: "[0-9]" }, + peg$c114 = function() { error('LuceneFuzzy'); }, - peg$c110 = function() { + peg$c115 = function() { error('LuceneProximity'); }, - peg$c111 = "^", - peg$c112 = { type: "literal", value: "^", description: "\"^\"" }, - peg$c113 = function() { + peg$c116 = "^", + peg$c117 = { type: "literal", value: "^", description: "\"^\"" }, + peg$c118 = function() { error('LuceneBoost'); }, - peg$c114 = function() { return char; }, - peg$c115 = "=", - peg$c116 = { type: "literal", value: "=", description: "\"=\"" }, - peg$c117 = "{", - peg$c118 = { type: "literal", value: "{", description: "\"{\"" }, - peg$c119 = "}", - peg$c120 = { type: "literal", value: "}", description: "\"}\"" }, - peg$c121 = "[", - peg$c122 = { type: "literal", value: "[", description: "\"[\"" }, - peg$c123 = "]", - peg$c124 = { type: "literal", value: "]", description: "\"]\"" }, - peg$c125 = "TO", - peg$c126 = { type: "literal", value: "TO", description: "\"TO\"" }, + peg$c119 = function() { return char; }, + peg$c120 = "=", + peg$c121 = { type: "literal", value: "=", description: "\"=\"" }, + peg$c122 = "[", + peg$c123 = { type: "literal", value: "[", description: "\"[\"" }, + peg$c124 = "]", + peg$c125 = { type: "literal", value: "]", description: "\"]\"" }, + peg$c126 = "TO", + peg$c127 = { type: "literal", value: "TO", description: "\"TO\"" }, peg$currPos = 0, peg$savedPos = 0, @@ -698,6 +714,107 @@ module.exports = (function() { peg$currPos = s0; s0 = peg$FAILED; } + if (s0 === peg$FAILED) { + s0 = peg$parseNestedQuery(); + } + + return s0; + } + + function peg$parseNestedQuery() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + s1 = peg$parseField(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c10; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c11); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseSpace(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseSpace(); + } + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 123) { + s5 = peg$c12; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c13); } + } + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseSpace(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseSpace(); + } + if (s6 !== peg$FAILED) { + s7 = peg$parseOrQuery(); + if (s7 !== peg$FAILED) { + s8 = peg$parseOptionalSpace(); + if (s8 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s9 = peg$c14; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c15); } + } + if (s9 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c16(s1, s7, s8); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } if (s0 === peg$FAILED) { s0 = peg$parseExpression(); } @@ -727,7 +844,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c10); } + if (peg$silentFails === 0) { peg$fail(peg$c17); } } return s0; @@ -758,7 +875,7 @@ module.exports = (function() { s5 = peg$parseLiteral(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c11(s1, s3, s5); + s1 = peg$c18(s1, s3, s5); s0 = s1; } else { peg$currPos = s0; @@ -798,11 +915,11 @@ module.exports = (function() { } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c12; + s3 = peg$c10; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } if (s3 !== peg$FAILED) { s4 = []; @@ -815,7 +932,7 @@ module.exports = (function() { s5 = peg$parseListOfValues(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c14(s1, s5); + s1 = peg$c19(s1, s5); s0 = s1; } else { peg$currPos = s0; @@ -848,7 +965,7 @@ module.exports = (function() { s1 = peg$parseValue(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c15(s1); + s1 = peg$c20(s1); } s0 = s1; @@ -887,7 +1004,7 @@ module.exports = (function() { } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c16(s3, s4); + s1 = peg$c21(s3, s4); s0 = s1; } else { peg$currPos = s0; @@ -927,7 +1044,7 @@ module.exports = (function() { s3 = peg$parseOrListOfValues(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c17(s1, s3); + s1 = peg$c22(s1, s3); s0 = s1; } else { peg$currPos = s0; @@ -959,7 +1076,7 @@ module.exports = (function() { s3 = peg$parseAndListOfValues(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c18(s1, s3); + s1 = peg$c23(s1, s3); s0 = s1; } else { peg$currPos = s0; @@ -989,7 +1106,7 @@ module.exports = (function() { s2 = peg$parseListOfValues(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c19(s2); + s1 = peg$c24(s2); s0 = s1; } else { peg$currPos = s0; @@ -1014,7 +1131,7 @@ module.exports = (function() { s1 = peg$parseQuotedString(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c21(s1); + s1 = peg$c26(s1); } s0 = s1; if (s0 === peg$FAILED) { @@ -1022,14 +1139,14 @@ module.exports = (function() { s1 = peg$parseUnquotedLiteral(); if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c22(s1); + s1 = peg$c27(s1); } s0 = s1; } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c20); } + if (peg$silentFails === 0) { peg$fail(peg$c25); } } return s0; @@ -1051,12 +1168,12 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c24) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c29) { s2 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c25); } + if (peg$silentFails === 0) { peg$fail(peg$c30); } } if (s2 !== peg$FAILED) { s3 = []; @@ -1110,7 +1227,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c23); } + if (peg$silentFails === 0) { peg$fail(peg$c28); } } return s0; @@ -1132,12 +1249,12 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 3).toLowerCase() === peg$c27) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c32) { s2 = input.substr(peg$currPos, 3); peg$currPos += 3; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } + if (peg$silentFails === 0) { peg$fail(peg$c33); } } if (s2 !== peg$FAILED) { s3 = []; @@ -1191,7 +1308,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c26); } + if (peg$silentFails === 0) { peg$fail(peg$c31); } } return s0; @@ -1202,12 +1319,12 @@ module.exports = (function() { peg$silentFails++; s0 = peg$currPos; - if (input.substr(peg$currPos, 3).toLowerCase() === peg$c30) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c35) { s1 = input.substr(peg$currPos, 3); peg$currPos += 3; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c31); } + if (peg$silentFails === 0) { peg$fail(peg$c36); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1257,7 +1374,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c29); } + if (peg$silentFails === 0) { peg$fail(peg$c34); } } return s0; @@ -1274,7 +1391,7 @@ module.exports = (function() { peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c32); } + if (peg$silentFails === 0) { peg$fail(peg$c37); } } return s0; @@ -1285,11 +1402,11 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c33; + s1 = peg$c38; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1309,15 +1426,15 @@ module.exports = (function() { } if (s4 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 34) { - s5 = peg$c33; + s5 = peg$c38; peg$currPos++; } else { s5 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c35(s2, s3, s4); + s1 = peg$c40(s2, s3, s4); s0 = s1; } else { peg$currPos = s0; @@ -1342,11 +1459,11 @@ module.exports = (function() { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 34) { - s1 = peg$c33; + s1 = peg$c38; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s1 !== peg$FAILED) { s2 = []; @@ -1357,15 +1474,15 @@ module.exports = (function() { } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 34) { - s3 = peg$c33; + s3 = peg$c38; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c36(s2); + s1 = peg$c41(s2); s0 = s1; } else { peg$currPos = s0; @@ -1391,23 +1508,23 @@ module.exports = (function() { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c37; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s1 !== peg$FAILED) { - if (peg$c39.test(input.charAt(peg$currPos))) { + if (peg$c44.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c40); } + if (peg$silentFails === 0) { peg$fail(peg$c45); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c41(s2); + s1 = peg$c46(s2); s0 = s1; } else { peg$currPos = s0; @@ -1430,16 +1547,16 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (peg$c42.test(input.charAt(peg$currPos))) { + if (peg$c47.test(input.charAt(peg$currPos))) { s2 = input.charAt(peg$currPos); peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c43); } + if (peg$silentFails === 0) { peg$fail(peg$c48); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c41(s2); + s1 = peg$c46(s2); s0 = s1; } else { peg$currPos = s0; @@ -1476,7 +1593,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c35(s1, s2, s3); + s1 = peg$c40(s1, s2, s3); s0 = s1; } else { peg$currPos = s0; @@ -1504,7 +1621,7 @@ module.exports = (function() { } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c44(s1); + s1 = peg$c49(s1); } s0 = s1; } @@ -1562,11 +1679,11 @@ module.exports = (function() { peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c45); } + if (peg$silentFails === 0) { peg$fail(peg$c50); } } if (s4 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c41(s4); + s1 = peg$c46(s4); s0 = s1; } else { peg$currPos = s0; @@ -1597,15 +1714,15 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 42) { - s1 = peg$c46; + s1 = peg$c51; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } + if (peg$silentFails === 0) { peg$fail(peg$c52); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c48(); + s1 = peg$c53(); } s0 = s1; @@ -1633,7 +1750,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c35(s1, s2, s3); + s1 = peg$c40(s1, s2, s3); s0 = s1; } else { peg$currPos = s0; @@ -1663,44 +1780,44 @@ module.exports = (function() { var s0, s1; s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c49) { - s1 = peg$c49; + if (input.substr(peg$currPos, 2) === peg$c54) { + s1 = peg$c54; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c50); } + if (peg$silentFails === 0) { peg$fail(peg$c55); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c51(); + s1 = peg$c56(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c52) { - s1 = peg$c52; + if (input.substr(peg$currPos, 2) === peg$c57) { + s1 = peg$c57; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c53); } + if (peg$silentFails === 0) { peg$fail(peg$c58); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c54(); + s1 = peg$c59(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c55) { - s1 = peg$c55; + if (input.substr(peg$currPos, 2) === peg$c60) { + s1 = peg$c60; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c56); } + if (peg$silentFails === 0) { peg$fail(peg$c61); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c57(); + s1 = peg$c62(); } s0 = s1; } @@ -1714,17 +1831,17 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c37; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s1 !== peg$FAILED) { s2 = peg$parseSpecialCharacter(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c41(s2); + s1 = peg$c46(s2); s0 = s1; } else { peg$currPos = s0; @@ -1743,41 +1860,41 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c37; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2).toLowerCase() === peg$c24) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c29) { s2 = input.substr(peg$currPos, 2); peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c25); } + if (peg$silentFails === 0) { peg$fail(peg$c30); } } if (s2 === peg$FAILED) { - if (input.substr(peg$currPos, 3).toLowerCase() === peg$c27) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c32) { s2 = input.substr(peg$currPos, 3); peg$currPos += 3; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c28); } + if (peg$silentFails === 0) { peg$fail(peg$c33); } } if (s2 === peg$FAILED) { - if (input.substr(peg$currPos, 3).toLowerCase() === peg$c30) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c35) { s2 = input.substr(peg$currPos, 3); peg$currPos += 3; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c31); } + if (peg$silentFails === 0) { peg$fail(peg$c36); } } } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c58(s2); + s1 = peg$c63(s2); s0 = s1; } else { peg$currPos = s0; @@ -1808,12 +1925,12 @@ module.exports = (function() { function peg$parseSpecialCharacter() { var s0; - if (peg$c59.test(input.charAt(peg$currPos))) { + if (peg$c64.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c60); } + if (peg$silentFails === 0) { peg$fail(peg$c65); } } return s0; @@ -1823,58 +1940,58 @@ module.exports = (function() { var s0, s1; s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c61) { - s1 = peg$c61; + if (input.substr(peg$currPos, 2) === peg$c66) { + s1 = peg$c66; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c62); } + if (peg$silentFails === 0) { peg$fail(peg$c67); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c63(); + s1 = peg$c68(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; - if (input.substr(peg$currPos, 2) === peg$c64) { - s1 = peg$c64; + if (input.substr(peg$currPos, 2) === peg$c69) { + s1 = peg$c69; peg$currPos += 2; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c65); } + if (peg$silentFails === 0) { peg$fail(peg$c70); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c66(); + s1 = peg$c71(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 60) { - s1 = peg$c67; + s1 = peg$c72; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c68); } + if (peg$silentFails === 0) { peg$fail(peg$c73); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c69(); + s1 = peg$c74(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 62) { - s1 = peg$c70; + s1 = peg$c75; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c71); } + if (peg$silentFails === 0) { peg$fail(peg$c76); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c72(); + s1 = peg$c77(); } s0 = s1; } @@ -1888,17 +2005,17 @@ module.exports = (function() { var s0, s1; peg$silentFails++; - if (peg$c74.test(input.charAt(peg$currPos))) { + if (peg$c79.test(input.charAt(peg$currPos))) { s0 = input.charAt(peg$currPos); peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c75); } + if (peg$silentFails === 0) { peg$fail(peg$c80); } } peg$silentFails--; if (s0 === peg$FAILED) { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c73); } + if (peg$silentFails === 0) { peg$fail(peg$c78); } } return s0; @@ -1909,23 +2026,23 @@ module.exports = (function() { s0 = peg$currPos; peg$savedPos = peg$currPos; - s1 = peg$c76(); + s1 = peg$c81(); if (s1) { s1 = void 0; } else { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 14) === peg$c77) { - s2 = peg$c77; + if (input.substr(peg$currPos, 14) === peg$c82) { + s2 = peg$c82; peg$currPos += 14; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c78); } + if (peg$silentFails === 0) { peg$fail(peg$c83); } } if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c79(); + s1 = peg$c84(); s0 = s1; } else { peg$currPos = s0; @@ -1950,12 +2067,12 @@ module.exports = (function() { s2 = peg$parseSpace(); } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c80) { - s2 = peg$c80; + if (input.substr(peg$currPos, 2) === peg$c85) { + s2 = peg$c85; peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c81); } + if (peg$silentFails === 0) { peg$fail(peg$c86); } } if (s2 !== peg$FAILED) { s3 = []; @@ -1966,7 +2083,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c82(); + s1 = peg$c87(); s0 = s1; } else { peg$currPos = s0; @@ -1995,12 +2112,12 @@ module.exports = (function() { s2 = peg$parseSpace(); } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c83) { - s2 = peg$c83; + if (input.substr(peg$currPos, 2) === peg$c88) { + s2 = peg$c88; peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c84); } + if (peg$silentFails === 0) { peg$fail(peg$c89); } } if (s2 !== peg$FAILED) { s3 = []; @@ -2011,7 +2128,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c85(); + s1 = peg$c90(); s0 = s1; } else { peg$currPos = s0; @@ -2028,15 +2145,15 @@ module.exports = (function() { if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 43) { - s1 = peg$c86; + s1 = peg$c91; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c87); } + if (peg$silentFails === 0) { peg$fail(peg$c92); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c85(); + s1 = peg$c90(); } s0 = s1; } @@ -2049,29 +2166,29 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 45) { - s1 = peg$c88; + s1 = peg$c93; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c89); } + if (peg$silentFails === 0) { peg$fail(peg$c94); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c90(); + s1 = peg$c95(); } s0 = s1; if (s0 === peg$FAILED) { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 33) { - s1 = peg$c91; + s1 = peg$c96; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c92); } + if (peg$silentFails === 0) { peg$fail(peg$c97); } } if (s1 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c90(); + s1 = peg$c95(); } s0 = s1; } @@ -2107,11 +2224,11 @@ module.exports = (function() { } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c12; + s3 = peg$c10; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } if (s3 !== peg$FAILED) { s4 = []; @@ -2176,12 +2293,12 @@ module.exports = (function() { var s0, s1, s2, s3, s4, s5; s0 = peg$currPos; - if (input.substr(peg$currPos, 8) === peg$c93) { - s1 = peg$c93; + if (input.substr(peg$currPos, 8) === peg$c98) { + s1 = peg$c98; peg$currPos += 8; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c94); } + if (peg$silentFails === 0) { peg$fail(peg$c99); } } if (s1 !== peg$FAILED) { s2 = []; @@ -2192,11 +2309,11 @@ module.exports = (function() { } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 58) { - s3 = peg$c12; + s3 = peg$c10; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } if (s3 !== peg$FAILED) { s4 = []; @@ -2209,7 +2326,7 @@ module.exports = (function() { s5 = peg$parseLuceneLiteral(); if (s5 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c95(); + s1 = peg$c100(); s0 = s1; } else { peg$currPos = s0; @@ -2251,7 +2368,7 @@ module.exports = (function() { s3 = peg$parseLuceneLiteral(); if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c96(); + s1 = peg$c101(); s0 = s1; } else { peg$currPos = s0; @@ -2285,7 +2402,7 @@ module.exports = (function() { s6 = peg$parseLuceneRangeEnd(); if (s6 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c96(); + s1 = peg$c101(); s0 = s1; } else { peg$currPos = s0; @@ -2324,11 +2441,11 @@ module.exports = (function() { s2 = peg$parseLuceneUnquotedCharacter(); if (s2 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 42) { - s2 = peg$c46; + s2 = peg$c51; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } + if (peg$silentFails === 0) { peg$fail(peg$c52); } } } while (s2 !== peg$FAILED) { @@ -2336,21 +2453,21 @@ module.exports = (function() { s2 = peg$parseLuceneUnquotedCharacter(); if (s2 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 42) { - s2 = peg$c46; + s2 = peg$c51; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } + if (peg$silentFails === 0) { peg$fail(peg$c52); } } } } if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 63) { - s2 = peg$c97; + s2 = peg$c102; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c98); } + if (peg$silentFails === 0) { peg$fail(peg$c103); } } if (s2 !== peg$FAILED) { s3 = []; @@ -2361,7 +2478,7 @@ module.exports = (function() { } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c99(); + s1 = peg$c104(); s0 = s1; } else { peg$currPos = s0; @@ -2384,42 +2501,42 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 47) { - s1 = peg$c100; + s1 = peg$c105; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c101); } + if (peg$silentFails === 0) { peg$fail(peg$c106); } } if (s1 !== peg$FAILED) { s2 = []; - if (peg$c102.test(input.charAt(peg$currPos))) { + if (peg$c107.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c103); } + if (peg$silentFails === 0) { peg$fail(peg$c108); } } while (s3 !== peg$FAILED) { s2.push(s3); - if (peg$c102.test(input.charAt(peg$currPos))) { + if (peg$c107.test(input.charAt(peg$currPos))) { s3 = input.charAt(peg$currPos); peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c103); } + if (peg$silentFails === 0) { peg$fail(peg$c108); } } } if (s2 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 47) { - s3 = peg$c100; + s3 = peg$c105; peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c101); } + if (peg$silentFails === 0) { peg$fail(peg$c106); } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c104(); + s1 = peg$c109(); s0 = s1; } else { peg$currPos = s0; @@ -2444,34 +2561,34 @@ module.exports = (function() { s1 = peg$parseLuceneUnquotedLiteral(); if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 126) { - s2 = peg$c105; + s2 = peg$c110; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c106); } + if (peg$silentFails === 0) { peg$fail(peg$c111); } } if (s2 !== peg$FAILED) { s3 = []; - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } while (s4 !== peg$FAILED) { s3.push(s4); - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c109(); + s1 = peg$c114(); s0 = s1; } else { peg$currPos = s0; @@ -2496,34 +2613,34 @@ module.exports = (function() { s1 = peg$parseQuotedString(); if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 126) { - s2 = peg$c105; + s2 = peg$c110; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c106); } + if (peg$silentFails === 0) { peg$fail(peg$c111); } } if (s2 !== peg$FAILED) { s3 = []; - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } while (s4 !== peg$FAILED) { s3.push(s4); - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c110(); + s1 = peg$c115(); s0 = s1; } else { peg$currPos = s0; @@ -2548,34 +2665,34 @@ module.exports = (function() { s1 = peg$parseLuceneLiteral(); if (s1 !== peg$FAILED) { if (input.charCodeAt(peg$currPos) === 94) { - s2 = peg$c111; + s2 = peg$c116; peg$currPos++; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c112); } + if (peg$silentFails === 0) { peg$fail(peg$c117); } } if (s2 !== peg$FAILED) { s3 = []; - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } while (s4 !== peg$FAILED) { s3.push(s4); - if (peg$c107.test(input.charAt(peg$currPos))) { + if (peg$c112.test(input.charAt(peg$currPos))) { s4 = input.charAt(peg$currPos); peg$currPos++; } else { s4 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c108); } + if (peg$silentFails === 0) { peg$fail(peg$c113); } } } if (s3 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c113(); + s1 = peg$c118(); s0 = s1; } else { peg$currPos = s0; @@ -2656,7 +2773,7 @@ module.exports = (function() { peg$currPos++; } else { s3 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c45); } + if (peg$silentFails === 0) { peg$fail(peg$c50); } } if (s3 !== peg$FAILED) { s1 = [s1, s2, s3]; @@ -2707,17 +2824,17 @@ module.exports = (function() { s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 92) { - s1 = peg$c37; + s1 = peg$c42; peg$currPos++; } else { s1 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s1 !== peg$FAILED) { s2 = peg$parseLuceneSpecialCharacter(); if (s2 !== peg$FAILED) { peg$savedPos = s0; - s1 = peg$c114(); + s1 = peg$c119(); s0 = s1; } else { peg$currPos = s0; @@ -2735,51 +2852,51 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 43) { - s0 = peg$c86; + s0 = peg$c91; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c87); } + if (peg$silentFails === 0) { peg$fail(peg$c92); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 45) { - s0 = peg$c88; + s0 = peg$c93; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c89); } + if (peg$silentFails === 0) { peg$fail(peg$c94); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 61) { - s0 = peg$c115; + s0 = peg$c120; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c116); } + if (peg$silentFails === 0) { peg$fail(peg$c121); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 62) { - s0 = peg$c70; + s0 = peg$c75; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c71); } + if (peg$silentFails === 0) { peg$fail(peg$c76); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 60) { - s0 = peg$c67; + s0 = peg$c72; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c68); } + if (peg$silentFails === 0) { peg$fail(peg$c73); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 33) { - s0 = peg$c91; + s0 = peg$c96; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c92); } + if (peg$silentFails === 0) { peg$fail(peg$c97); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 40) { @@ -2799,99 +2916,99 @@ module.exports = (function() { } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 123) { - s0 = peg$c117; + s0 = peg$c12; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c118); } + if (peg$silentFails === 0) { peg$fail(peg$c13); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 125) { - s0 = peg$c119; + s0 = peg$c14; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c120); } + if (peg$silentFails === 0) { peg$fail(peg$c15); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 91) { - s0 = peg$c121; + s0 = peg$c122; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c122); } + if (peg$silentFails === 0) { peg$fail(peg$c123); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 93) { - s0 = peg$c123; + s0 = peg$c124; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c124); } + if (peg$silentFails === 0) { peg$fail(peg$c125); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 94) { - s0 = peg$c111; + s0 = peg$c116; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c112); } + if (peg$silentFails === 0) { peg$fail(peg$c117); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 34) { - s0 = peg$c33; + s0 = peg$c38; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c34); } + if (peg$silentFails === 0) { peg$fail(peg$c39); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 126) { - s0 = peg$c105; + s0 = peg$c110; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c106); } + if (peg$silentFails === 0) { peg$fail(peg$c111); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 42) { - s0 = peg$c46; + s0 = peg$c51; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c47); } + if (peg$silentFails === 0) { peg$fail(peg$c52); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 63) { - s0 = peg$c97; + s0 = peg$c102; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c98); } + if (peg$silentFails === 0) { peg$fail(peg$c103); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 58) { - s0 = peg$c12; + s0 = peg$c10; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c13); } + if (peg$silentFails === 0) { peg$fail(peg$c11); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 92) { - s0 = peg$c37; + s0 = peg$c42; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c38); } + if (peg$silentFails === 0) { peg$fail(peg$c43); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 47) { - s0 = peg$c100; + s0 = peg$c105; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c101); } + if (peg$silentFails === 0) { peg$fail(peg$c106); } } } } @@ -2931,12 +3048,12 @@ module.exports = (function() { s1 = peg$FAILED; } if (s1 !== peg$FAILED) { - if (input.substr(peg$currPos, 2) === peg$c125) { - s2 = peg$c125; + if (input.substr(peg$currPos, 2) === peg$c126) { + s2 = peg$c126; peg$currPos += 2; } else { s2 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c126); } + if (peg$silentFails === 0) { peg$fail(peg$c127); } } if (s2 !== peg$FAILED) { s3 = []; @@ -2972,19 +3089,19 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 91) { - s0 = peg$c121; + s0 = peg$c122; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c122); } + if (peg$silentFails === 0) { peg$fail(peg$c123); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 123) { - s0 = peg$c117; + s0 = peg$c12; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c118); } + if (peg$silentFails === 0) { peg$fail(peg$c13); } } } @@ -2995,19 +3112,19 @@ module.exports = (function() { var s0; if (input.charCodeAt(peg$currPos) === 93) { - s0 = peg$c123; + s0 = peg$c124; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c124); } + if (peg$silentFails === 0) { peg$fail(peg$c125); } } if (s0 === peg$FAILED) { if (input.charCodeAt(peg$currPos) === 125) { - s0 = peg$c119; + s0 = peg$c14; peg$currPos++; } else { s0 = peg$FAILED; - if (peg$silentFails === 0) { peg$fail(peg$c120); } + if (peg$silentFails === 0) { peg$fail(peg$c15); } } } diff --git a/packages/kbn-es-query/src/kuery/ast/kuery.peg b/packages/kbn-es-query/src/kuery/ast/kuery.peg index ec8368d2a921d..389b9a82d2c76 100644 --- a/packages/kbn-es-query/src/kuery/ast/kuery.peg +++ b/packages/kbn-es-query/src/kuery/ast/kuery.peg @@ -59,7 +59,26 @@ SubQuery } return query; } - / Expression + / NestedQuery + +NestedQuery + = field:Field Space* ':' Space* '{' Space* query:OrQuery trailing:OptionalSpace '}' { + if (query.type === 'cursor') { + return { + ...query, + nestedPath: query.nestedPath ? `${field.value}.${query.nestedPath}` : field.value, + } + }; + + if (trailing.type === 'cursor') { + return { + ...trailing, + suggestionTypes: ['conjunction'] + }; + } + return buildFunctionNode('nested', [field, query]); + } + / Expression Expression = FieldRangeExpression @@ -272,7 +291,7 @@ Keyword = Or / And / Not SpecialCharacter - = [\\():<>"*] + = [\\():<>"*{}] RangeOperator = '<=' { return 'lte'; } diff --git a/packages/kbn-es-query/src/kuery/errors/index.test.js b/packages/kbn-es-query/src/kuery/errors/index.test.js index 91c90b2cce98b..d8040e464b696 100644 --- a/packages/kbn-es-query/src/kuery/errors/index.test.js +++ b/packages/kbn-es-query/src/kuery/errors/index.test.js @@ -25,7 +25,7 @@ describe('kql syntax errors', () => { it('should throw an error for a field query missing a value', () => { expect(() => { fromKueryExpression('response:'); - }).toThrow('Expected "(", value, whitespace but end of input found.\n' + + }).toThrow('Expected "(", "{", value, whitespace but end of input found.\n' + 'response:\n' + '---------^'); }); @@ -65,7 +65,7 @@ describe('kql syntax errors', () => { it('should throw an error for unbalanced quotes', () => { expect(() => { fromKueryExpression('foo:"ba '); - }).toThrow('Expected "(", value, whitespace but "\"" found.\n' + + }).toThrow('Expected "(", "{", value, whitespace but """ found.\n' + 'foo:"ba \n' + '----^'); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js b/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js index 0eaa69a3243e0..ee4cfab94e614 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js @@ -72,6 +72,21 @@ describe('kuery functions', function () { expect(exists.toElasticsearchQuery) .withArgs(existsNode, indexPattern).to.throwException(/Exists query does not support scripted fields/); }); + + it('should use a provided nested context to create a full field name', function () { + const expected = { + exists: { field: 'nestedField.response' } + }; + + const existsNode = nodeTypes.function.buildNode('exists', 'response'); + const result = exists.toElasticsearchQuery( + existsNode, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(_.isEqual(expected, result)).to.be(true); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js index 2f8b784d6f6e8..7afa0fcce1bfe 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js @@ -102,6 +102,19 @@ describe('kuery functions', function () { expect(geoBoundingBox.toElasticsearchQuery) .withArgs(node, indexPattern).to.throwException(/Geo bounding box query does not support scripted fields/); }); + + it('should use a provided nested context to create a full field name', function () { + const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); + const result = geoBoundingBox.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(result).to.have.property('geo_bounding_box'); + expect(result.geo_bounding_box).to.have.property('nestedField.geo'); + }); + }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js index 1c6a2e0e47fde..c1f2fae0bb3e1 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js @@ -114,6 +114,18 @@ describe('kuery functions', function () { expect(geoPolygon.toElasticsearchQuery) .withArgs(node, indexPattern).to.throwException(/Geo polygon query does not support scripted fields/); }); + + it('should use a provided nested context to create a full field name', function () { + const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); + const result = geoPolygon.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(result).to.have.property('geo_polygon'); + expect(result.geo_polygon).to.have.property('nestedField.geo'); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/is.js b/packages/kbn-es-query/src/kuery/functions/__tests__/is.js index 18652c9faccb8..b2f3d7ec16a65 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/is.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/is.js @@ -245,6 +245,66 @@ describe('kuery functions', function () { expect(result).to.eql(expected); }); + it('should use a provided nested context to create a full field name', function () { + const expected = { + bool: { + should: [ + { match: { 'nestedField.extension': 'jpg' } }, + ], + minimum_should_match: 1 + } + }; + + const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); + const result = is.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(result).to.eql(expected); + }); + + it('should support wildcard field names', function () { + const expected = { + bool: { + should: [ + { match: { extension: 'jpg' } }, + ], + minimum_should_match: 1 + } + }; + + const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg'); + const result = is.toElasticsearchQuery(node, indexPattern); + expect(result).to.eql(expected); + }); + + it('should automatically add a nested query when a wildcard field name covers a nested field', () => { + const expected = { + bool: { + should: [ + { + nested: { + path: 'nestedField.nestedChild', + query: { + match: { + 'nestedField.nestedChild.doublyNestedChild': 'foo' + } + }, + score_mode: 'none' + } + } + ], + minimum_should_match: 1 + } + }; + + + const node = nodeTypes.function.buildNode('is', '*doublyNested*', 'foo'); + const result = is.toElasticsearchQuery(node, indexPattern); + expect(result).to.eql(expected); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/nested.js b/packages/kbn-es-query/src/kuery/functions/__tests__/nested.js new file mode 100644 index 0000000000000..5ba73e485ddf1 --- /dev/null +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/nested.js @@ -0,0 +1,68 @@ +/* + * 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 expect from '@kbn/expect'; +import * as nested from '../nested'; +import { nodeTypes } from '../../node_types'; +import * as ast from '../../ast'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; + +let indexPattern; + +const childNode = nodeTypes.function.buildNode('is', 'child', 'foo'); + +describe('kuery functions', function () { + describe('nested', function () { + + beforeEach(() => { + indexPattern = indexPatternResponse; + }); + + describe('buildNodeParams', function () { + + it('arguments should contain the unmodified child nodes', function () { + const result = nested.buildNodeParams('nestedField', childNode); + const { arguments: [ resultPath, resultChildNode ] } = result; + expect(ast.toElasticsearchQuery(resultPath)).to.be('nestedField'); + expect(resultChildNode).to.be(childNode); + }); + }); + + describe('toElasticsearchQuery', function () { + + it('should wrap subqueries in an ES nested query', function () { + const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode); + const result = nested.toElasticsearchQuery(node, indexPattern); + expect(result).to.only.have.keys('nested'); + expect(result.nested.path).to.be('nestedField'); + expect(result.nested.score_mode).to.be('none'); + }); + + it('should pass the nested path to subqueries so the full field name can be used', function () { + const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode); + const result = nested.toElasticsearchQuery(node, indexPattern); + const expectedSubQuery = ast.toElasticsearchQuery( + nodeTypes.function.buildNode('is', 'nestedField.child', 'foo') + ); + expect(result.nested.query).to.eql(expectedSubQuery); + }); + + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/range.js b/packages/kbn-es-query/src/kuery/functions/__tests__/range.js index 4f290206c8bfb..2361e8bb66769 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/range.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/range.js @@ -181,6 +181,60 @@ describe('kuery functions', function () { expect(result).to.eql(expected); }); + it('should use a provided nested context to create a full field name', function () { + const expected = { + bool: { + should: [ + { + range: { + 'nestedField.bytes': { + gt: 1000, + lt: 8000 + } + } + } + ], + minimum_should_match: 1 + } + }; + + const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 }); + const result = range.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + expect(result).to.eql(expected); + }); + + it('should automatically add a nested query when a wildcard field name covers a nested field', function () { + const expected = { + bool: { + should: [ + { + nested: { + path: 'nestedField.nestedChild', + query: { + range: { + 'nestedField.nestedChild.doublyNestedChild': { + gt: 1000, + lt: 8000 + } + } + }, + score_mode: 'none' + } + } + ], + minimum_should_match: 1 + } + }; + + const node = nodeTypes.function.buildNode('range', '*doublyNested*', { gt: 1000, lt: 8000 }); + const result = range.toElasticsearchQuery(node, indexPattern); + expect(result).to.eql(expected); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js b/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js new file mode 100644 index 0000000000000..dae15979a161c --- /dev/null +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js @@ -0,0 +1,88 @@ +/* + * 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 expect from '@kbn/expect'; +import { nodeTypes } from '../../../node_types'; +import indexPatternResponse from '../../../../__fixtures__/index_pattern_response.json'; +import { getFullFieldNameNode } from '../../utils/get_full_field_name_node'; + +let indexPattern; + +describe('getFullFieldNameNode', function () { + + beforeEach(() => { + indexPattern = indexPatternResponse; + }); + + it('should return unchanged name node if no nested path is passed in', () => { + const nameNode = nodeTypes.literal.buildNode('notNested'); + const result = getFullFieldNameNode(nameNode, indexPattern); + expect(result).to.eql(nameNode); + }); + + it('should add the nested path if it is valid according to the index pattern', () => { + const nameNode = nodeTypes.literal.buildNode('child'); + const result = getFullFieldNameNode(nameNode, indexPattern, 'nestedField'); + expect(result).to.eql(nodeTypes.literal.buildNode('nestedField.child')); + }); + + it('should throw an error if a path is provided for a non-nested field', () => { + const nameNode = nodeTypes.literal.buildNode('os'); + expect(getFullFieldNameNode) + .withArgs(nameNode, indexPattern, 'machine') + .to + .throwException(/machine.os is not a nested field but is in nested group "machine" in the KQL expression/); + }); + + it('should throw an error if a nested field is not passed with a path', () => { + const nameNode = nodeTypes.literal.buildNode('nestedField.child'); + expect(getFullFieldNameNode) + .withArgs(nameNode, indexPattern) + .to + .throwException(/nestedField.child is a nested field, but is not in a nested group in the KQL expression./); + }); + + it('should throw an error if a nested field is passed with the wrong path', () => { + const nameNode = nodeTypes.literal.buildNode('nestedChild.doublyNestedChild'); + expect(getFullFieldNameNode) + .withArgs(nameNode, indexPattern, 'nestedField') + .to + // eslint-disable-next-line max-len + .throwException(/Nested field nestedField.nestedChild.doublyNestedChild is being queried with the incorrect nested path. The correct path is nestedField.nestedChild/); + }); + + it('should skip error checking for wildcard names', () => { + const nameNode = nodeTypes.wildcard.buildNode('nested*'); + const result = getFullFieldNameNode(nameNode, indexPattern); + expect(result).to.eql(nameNode); + }); + + it('should skip error checking if no index pattern is passed in', () => { + const nameNode = nodeTypes.literal.buildNode('os'); + expect(getFullFieldNameNode) + .withArgs(nameNode, null, 'machine') + .to + .not + .throwException(); + + const result = getFullFieldNameNode(nameNode, null, 'machine'); + expect(result).to.eql(nodeTypes.literal.buildNode('machine.os')); + }); + +}); diff --git a/packages/kbn-es-query/src/kuery/functions/and.js b/packages/kbn-es-query/src/kuery/functions/and.js index 68e125ea4de59..2650e83814b66 100644 --- a/packages/kbn-es-query/src/kuery/functions/and.js +++ b/packages/kbn-es-query/src/kuery/functions/and.js @@ -25,13 +25,13 @@ export function buildNodeParams(children) { }; } -export function toElasticsearchQuery(node, indexPattern, config) { +export function toElasticsearchQuery(node, indexPattern, config, context) { const children = node.arguments || []; return { bool: { filter: children.map((child) => { - return ast.toElasticsearchQuery(child, indexPattern, config); + return ast.toElasticsearchQuery(child, indexPattern, config, context); }) } }; diff --git a/packages/kbn-es-query/src/kuery/functions/exists.js b/packages/kbn-es-query/src/kuery/functions/exists.js index 64810fc2ba796..29940c8c2a425 100644 --- a/packages/kbn-es-query/src/kuery/functions/exists.js +++ b/packages/kbn-es-query/src/kuery/functions/exists.js @@ -26,9 +26,10 @@ export function buildNodeParams(fieldName) { }; } -export function toElasticsearchQuery(node, indexPattern) { +export function toElasticsearchQuery(node, indexPattern = null, config, context = {}) { const { arguments: [ fieldNameArg ] } = node; - const fieldName = literal.toElasticsearchQuery(fieldNameArg); + const fullFieldNameArg = { ...fieldNameArg, value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value }; + const fieldName = literal.toElasticsearchQuery(fullFieldNameArg); const field = get(indexPattern, 'fields', []).find(field => field.name === fieldName); if (field && field.scripted) { diff --git a/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js index eeab146f6f698..fd7e68e52e054 100644 --- a/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js +++ b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js @@ -34,9 +34,10 @@ export function buildNodeParams(fieldName, params) { }; } -export function toElasticsearchQuery(node, indexPattern) { +export function toElasticsearchQuery(node, indexPattern, config, context = {}) { const [ fieldNameArg, ...args ] = node.arguments; - const fieldName = nodeTypes.literal.toElasticsearchQuery(fieldNameArg); + const fullFieldNameArg = { ...fieldNameArg, value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value }; + const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg); const field = _.get(indexPattern, 'fields', []).find(field => field.name === fieldName); const queryParams = args.reduce((acc, arg) => { const snakeArgName = _.snakeCase(arg.name); @@ -57,4 +58,3 @@ export function toElasticsearchQuery(node, indexPattern) { }, }; } - diff --git a/packages/kbn-es-query/src/kuery/functions/geo_polygon.js b/packages/kbn-es-query/src/kuery/functions/geo_polygon.js index 2e9f10071ea32..7756ae731ce6c 100644 --- a/packages/kbn-es-query/src/kuery/functions/geo_polygon.js +++ b/packages/kbn-es-query/src/kuery/functions/geo_polygon.js @@ -33,12 +33,13 @@ export function buildNodeParams(fieldName, points) { }; } -export function toElasticsearchQuery(node, indexPattern) { +export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) { const [ fieldNameArg, ...points ] = node.arguments; - const fieldName = nodeTypes.literal.toElasticsearchQuery(fieldNameArg); + const fullFieldNameArg = { ...fieldNameArg, value: context.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value }; + const fieldName = nodeTypes.literal.toElasticsearchQuery(fullFieldNameArg); const field = get(indexPattern, 'fields', []).find(field => field.name === fieldName); const queryParams = { - points: points.map(ast.toElasticsearchQuery) + points: points.map((point) => { return ast.toElasticsearchQuery(point, indexPattern, config, context); }) }; if (field && field.scripted) { diff --git a/packages/kbn-es-query/src/kuery/functions/index.js b/packages/kbn-es-query/src/kuery/functions/index.js index 39b41f5301857..08b30f022f431 100644 --- a/packages/kbn-es-query/src/kuery/functions/index.js +++ b/packages/kbn-es-query/src/kuery/functions/index.js @@ -25,6 +25,7 @@ import * as range from './range'; import * as exists from './exists'; import * as geoBoundingBox from './geo_bounding_box'; import * as geoPolygon from './geo_polygon'; +import * as nested from './nested'; export const functions = { is, @@ -35,4 +36,5 @@ export const functions = { exists, geoBoundingBox, geoPolygon, + nested, }; diff --git a/packages/kbn-es-query/src/kuery/functions/is.js b/packages/kbn-es-query/src/kuery/functions/is.js index 690f98b08ba82..33ae2112e3c0c 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.js +++ b/packages/kbn-es-query/src/kuery/functions/is.js @@ -24,6 +24,7 @@ import * as wildcard from '../node_types/wildcard'; import { getPhraseScript } from '../../filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils/get_time_zone_from_settings'; +import { getFullFieldNameNode } from './utils/get_full_field_name_node'; export function buildNodeParams(fieldName, value, isPhrase = false) { if (_.isUndefined(fieldName)) { @@ -40,12 +41,13 @@ export function buildNodeParams(fieldName, value, isPhrase = false) { }; } -export function toElasticsearchQuery(node, indexPattern = null, config = {}) { +export function toElasticsearchQuery(node, indexPattern = null, config = {}, context = {}) { const { arguments: [fieldNameArg, valueArg, isPhraseArg] } = node; - const fieldName = ast.toElasticsearchQuery(fieldNameArg); + const fullFieldNameArg = getFullFieldNameNode(fieldNameArg, indexPattern, context.nested ? context.nested.path : undefined); + const fieldName = ast.toElasticsearchQuery(fullFieldNameArg); const value = !_.isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg; const type = isPhraseArg.value ? 'phrase' : 'best_fields'; - if (fieldNameArg.value === null) { + if (fullFieldNameArg.value === null) { if (valueArg.type === 'wildcard') { return { query_string: { @@ -63,7 +65,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { }; } - const fields = indexPattern ? getFields(fieldNameArg, indexPattern) : []; + const fields = indexPattern ? getFields(fullFieldNameArg, indexPattern) : []; // If no fields are found in the index pattern we send through the given field name as-is. We do this to preserve // the behaviour of lucene on dashboards where there are panels based on different index patterns that have different // fields. If a user queries on a field that exists in one pattern but not the other, the index pattern without the @@ -71,14 +73,14 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { // keep things familiar for now. if (fields && fields.length === 0) { fields.push({ - name: ast.toElasticsearchQuery(fieldNameArg), + name: ast.toElasticsearchQuery(fullFieldNameArg), scripted: false, }); } const isExistsQuery = valueArg.type === 'wildcard' && value === '*'; const isAllFieldsQuery = - (fieldNameArg.type === 'wildcard' && fieldName === '*') + (fullFieldNameArg.type === 'wildcard' && fieldName === '*') || (fields && indexPattern && fields.length === indexPattern.fields.length); const isMatchAllQuery = isExistsQuery && isAllFieldsQuery; @@ -87,6 +89,27 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { } const queries = fields.reduce((accumulator, field) => { + const wrapWithNestedQuery = (query) => { + // Wildcards can easily include nested and non-nested fields. There isn't a good way to let + // users handle this themselves so we automatically add nested queries in this scenario. + if ( + !(fullFieldNameArg.type === 'wildcard') + || !_.get(field, 'subType.nested') + || context.nested + ) { + return query; + } + else { + return { + nested: { + path: field.subType.nested.path, + query, + score_mode: 'none' + } + }; + } + }; + if (field.scripted) { // Exists queries don't make sense for scripted fields if (!isExistsQuery) { @@ -98,19 +121,19 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { } } else if (isExistsQuery) { - return [...accumulator, { + return [...accumulator, wrapWithNestedQuery({ exists: { field: field.name } - }]; + })]; } else if (valueArg.type === 'wildcard') { - return [...accumulator, { + return [...accumulator, wrapWithNestedQuery({ query_string: { fields: [field.name], query: wildcard.toQueryStringQuery(valueArg), } - }]; + })]; } /* If we detect that it's a date field and the user wants an exact date, we need to convert the query to both >= and <= the value provided to force a range query. This is because match and match_phrase queries do not accept a timezone parameter. @@ -118,7 +141,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { */ else if (field.type === 'date') { const timeZoneParam = config.dateFormatTZ ? { time_zone: getTimeZoneFromSettings(config.dateFormatTZ) } : {}; - return [...accumulator, { + return [...accumulator, wrapWithNestedQuery({ range: { [field.name]: { gte: value, @@ -126,15 +149,15 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { ...timeZoneParam, }, } - }]; + })]; } else { const queryType = type === 'phrase' ? 'match_phrase' : 'match'; - return [...accumulator, { + return [...accumulator, wrapWithNestedQuery({ [queryType]: { [field.name]: value } - }]; + })]; } }, []); @@ -146,4 +169,3 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { }; } - diff --git a/packages/kbn-es-query/src/kuery/functions/nested.js b/packages/kbn-es-query/src/kuery/functions/nested.js new file mode 100644 index 0000000000000..6237189e16311 --- /dev/null +++ b/packages/kbn-es-query/src/kuery/functions/nested.js @@ -0,0 +1,46 @@ +/* + * 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 * as ast from '../ast'; +import * as literal from '../node_types/literal'; + +export function buildNodeParams(path, child) { + const pathNode = typeof path === 'string' ? ast.fromLiteralExpression(path) : literal.buildNode(path); + return { + arguments: [pathNode, child], + }; +} + +export function toElasticsearchQuery(node, indexPattern, config, context = {}) { + if (!indexPattern) { + throw new Error('Cannot use nested queries without an index pattern'); + } + + const [path, child] = node.arguments; + const stringPath = ast.toElasticsearchQuery(path); + const fullPath = context.nested && context.nested.path ? `${context.nested.path}.${stringPath}` : stringPath; + + return { + nested: { + path: fullPath, + query: ast.toElasticsearchQuery(child, indexPattern, config, { ...context, nested: { path: fullPath } }), + score_mode: 'none', + }, + }; +} diff --git a/packages/kbn-es-query/src/kuery/functions/not.js b/packages/kbn-es-query/src/kuery/functions/not.js index d3ab14df16bcb..8a4b8c6060109 100644 --- a/packages/kbn-es-query/src/kuery/functions/not.js +++ b/packages/kbn-es-query/src/kuery/functions/not.js @@ -25,13 +25,12 @@ export function buildNodeParams(child) { }; } -export function toElasticsearchQuery(node, indexPattern, config) { +export function toElasticsearchQuery(node, indexPattern, config, context) { const [ argument ] = node.arguments; return { bool: { - must_not: ast.toElasticsearchQuery(argument, indexPattern, config) + must_not: ast.toElasticsearchQuery(argument, indexPattern, config, context) } }; } - diff --git a/packages/kbn-es-query/src/kuery/functions/or.js b/packages/kbn-es-query/src/kuery/functions/or.js index 918d46a6691de..9b27ac9151801 100644 --- a/packages/kbn-es-query/src/kuery/functions/or.js +++ b/packages/kbn-es-query/src/kuery/functions/or.js @@ -25,13 +25,13 @@ export function buildNodeParams(children) { }; } -export function toElasticsearchQuery(node, indexPattern, config) { +export function toElasticsearchQuery(node, indexPattern, config, context) { const children = node.arguments || []; return { bool: { should: children.map((child) => { - return ast.toElasticsearchQuery(child, indexPattern, config); + return ast.toElasticsearchQuery(child, indexPattern, config, context); }), minimum_should_match: 1, }, diff --git a/packages/kbn-es-query/src/kuery/functions/range.js b/packages/kbn-es-query/src/kuery/functions/range.js index df77baf4b0208..80181cfc003f1 100644 --- a/packages/kbn-es-query/src/kuery/functions/range.js +++ b/packages/kbn-es-query/src/kuery/functions/range.js @@ -23,6 +23,7 @@ import * as ast from '../ast'; import { getRangeScript } from '../../filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils/get_time_zone_from_settings'; +import { getFullFieldNameNode } from './utils/get_full_field_name_node'; export function buildNodeParams(fieldName, params) { params = _.pick(params, 'gt', 'lt', 'gte', 'lte', 'format'); @@ -36,9 +37,10 @@ export function buildNodeParams(fieldName, params) { }; } -export function toElasticsearchQuery(node, indexPattern = null, config = {}) { +export function toElasticsearchQuery(node, indexPattern = null, config = {}, context = {}) { const [ fieldNameArg, ...args ] = node.arguments; - const fields = indexPattern ? getFields(fieldNameArg, indexPattern) : []; + const fullFieldNameArg = getFullFieldNameNode(fieldNameArg, indexPattern, context.nested ? context.nested.path : undefined); + const fields = indexPattern ? getFields(fullFieldNameArg, indexPattern) : []; const namedArgs = extractArguments(args); const queryParams = _.mapValues(namedArgs, ast.toElasticsearchQuery); @@ -49,13 +51,34 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { // keep things familiar for now. if (fields && fields.length === 0) { fields.push({ - name: ast.toElasticsearchQuery(fieldNameArg), + name: ast.toElasticsearchQuery(fullFieldNameArg), scripted: false, }); } const queries = fields.map((field) => { + const wrapWithNestedQuery = (query) => { + // Wildcards can easily include nested and non-nested fields. There isn't a good way to let + // users handle this themselves so we automatically add nested queries in this scenario. + if ( + !fullFieldNameArg.type === 'wildcard' + || !_.get(field, 'subType.nested') + || context.nested + ) { + return query; + } + else { + return { + nested: { + path: field.subType.nested.path, + query, + score_mode: 'none' + } + }; + } + }; + if (field.scripted) { return { script: getRangeScript(field, queryParams), @@ -63,20 +86,20 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}) { } else if (field.type === 'date') { const timeZoneParam = config.dateFormatTZ ? { time_zone: getTimeZoneFromSettings(config.dateFormatTZ) } : {}; - return { + return wrapWithNestedQuery({ range: { [field.name]: { ...queryParams, ...timeZoneParam, } } - }; + }); } - return { + return wrapWithNestedQuery({ range: { [field.name]: queryParams } - }; + }); }); return { diff --git a/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.js b/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.js new file mode 100644 index 0000000000000..07540344b7955 --- /dev/null +++ b/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.js @@ -0,0 +1,63 @@ +/* + * 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 { getFields } from './get_fields'; + +export function getFullFieldNameNode(rootNameNode, indexPattern, nestedPath) { + const fullFieldNameNode = { + ...rootNameNode, + value: nestedPath ? `${nestedPath}.${rootNameNode.value}` : rootNameNode.value + }; + + // Wildcards can easily include nested and non-nested fields. There isn't a good way to let + // users handle this themselves so we automatically add nested queries in this scenario and skip the + // error checking below. + if (!indexPattern || (fullFieldNameNode.type === 'wildcard' && !nestedPath)) { + return fullFieldNameNode; + } + const fields = getFields(fullFieldNameNode, indexPattern); + + const errors = fields.reduce((acc, field) => { + const nestedPathFromField = field.subType && field.subType.nested ? field.subType.nested.path : undefined; + + if (nestedPath && !nestedPathFromField) { + return [...acc, `${field.name} is not a nested field but is in nested group "${nestedPath}" in the KQL expression.`]; + } + + if (nestedPathFromField && !nestedPath) { + return [...acc, `${field.name} is a nested field, but is not in a nested group in the KQL expression.`]; + } + + if (nestedPathFromField !== nestedPath) { + return [ + ...acc, + `Nested field ${field.name} is being queried with the incorrect nested path. The correct path is ${field.subType.nested.path}.` + ]; + } + + return acc; + }, []); + + if (errors.length > 0) { + throw new Error(errors.join('\n')); + } + + return fullFieldNameNode; +} diff --git a/packages/kbn-es-query/src/kuery/node_types/function.js b/packages/kbn-es-query/src/kuery/node_types/function.js index 6b10bb1f704c8..d54b7de6cd8ea 100644 --- a/packages/kbn-es-query/src/kuery/node_types/function.js +++ b/packages/kbn-es-query/src/kuery/node_types/function.js @@ -47,8 +47,7 @@ export function buildNodeWithArgumentNodes(functionName, argumentNodes) { }; } -export function toElasticsearchQuery(node, indexPattern, config = {}) { +export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) { const kueryFunction = functions[node.function]; - return kueryFunction.toElasticsearchQuery(node, indexPattern, config); + return kueryFunction.toElasticsearchQuery(node, indexPattern, config, context); } - diff --git a/packages/kbn-es-query/src/kuery/node_types/index.d.ts b/packages/kbn-es-query/src/kuery/node_types/index.d.ts index 0d1f2c28e39f0..daf8032f9fe0e 100644 --- a/packages/kbn-es-query/src/kuery/node_types/index.d.ts +++ b/packages/kbn-es-query/src/kuery/node_types/index.d.ts @@ -31,7 +31,8 @@ type FunctionName = | 'range' | 'exists' | 'geoBoundingBox' - | 'geoPolygon'; + | 'geoPolygon' + | 'nested'; interface FunctionTypeBuildNode { type: 'function'; diff --git a/packages/kbn-eslint-plugin-eslint/rules/__tests__/files/no_restricted_paths/server/index_patterns/index.js b/packages/kbn-eslint-plugin-eslint/rules/__tests__/files/no_restricted_paths/server/index_patterns/index.js new file mode 100644 index 0000000000000..d15de7d98a9e0 --- /dev/null +++ b/packages/kbn-eslint-plugin-eslint/rules/__tests__/files/no_restricted_paths/server/index_patterns/index.js @@ -0,0 +1 @@ +/* eslint-disable */ diff --git a/packages/kbn-eslint-plugin-eslint/rules/__tests__/no_restricted_paths.js b/packages/kbn-eslint-plugin-eslint/rules/__tests__/no_restricted_paths.js index f393a867d95e0..577be820ccc7b 100644 --- a/packages/kbn-eslint-plugin-eslint/rules/__tests__/no_restricted_paths.js +++ b/packages/kbn-eslint-plugin-eslint/rules/__tests__/no_restricted_paths.js @@ -172,6 +172,27 @@ ruleTester.run('@kbn/eslint/no-restricted-paths', rule, { }, ], }, + + { + // Check if dirs that start with 'index' work correctly. + code: 'import { X } from "./index_patterns"', + filename: path.join(__dirname, './files/no_restricted_paths/server/b.js'), + options: [ + { + basePath: __dirname, + zones: [ + { + target: ['files/no_restricted_paths/(public|server)/**/*'], + from: [ + 'files/no_restricted_paths/server/**/*', + '!files/no_restricted_paths/server/index.{ts,tsx}', + ], + allowSameFolder: true, + }, + ], + }, + ], + }, ], invalid: [ @@ -369,5 +390,34 @@ ruleTester.run('@kbn/eslint/no-restricted-paths', rule, { }, ], }, + + { + // Don't use index*. + // It won't work with dirs that start with 'index'. + code: 'import { X } from "./index_patterns"', + filename: path.join(__dirname, './files/no_restricted_paths/server/b.js'), + options: [ + { + basePath: __dirname, + zones: [ + { + target: ['files/no_restricted_paths/(public|server)/**/*'], + from: [ + 'files/no_restricted_paths/server/**/*', + '!files/no_restricted_paths/server/index*', + ], + allowSameFolder: true, + }, + ], + }, + ], + errors: [ + { + message: 'Unexpected path "./index_patterns" imported in restricted zone.', + line: 1, + column: 19, + }, + ], + }, ], }); diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index 1a6a8929cd6a0..383ba77f17779 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -22,32 +22,33 @@ import { ElasticsearchConfig, config } from './elasticsearch_config'; test('set correct defaults', () => { const configValue = new ElasticsearchConfig(config.schema.validate({})); expect(configValue).toMatchInlineSnapshot(` -ElasticsearchConfig { - "apiVersion": "master", - "customHeaders": Object {}, - "healthCheckDelay": "PT2.5S", - "hosts": Array [ - "http://localhost:9200", - ], - "logQueries": false, - "password": undefined, - "pingTimeout": "PT30S", - "requestHeadersWhitelist": Array [ - "authorization", - ], - "requestTimeout": "PT30S", - "shardTimeout": "PT30S", - "sniffInterval": false, - "sniffOnConnectionFault": false, - "sniffOnStart": false, - "ssl": Object { - "alwaysPresentCertificate": true, - "certificateAuthorities": undefined, - "verificationMode": "full", - }, - "username": undefined, -} -`); + ElasticsearchConfig { + "apiVersion": "master", + "customHeaders": Object {}, + "healthCheckDelay": "PT2.5S", + "hosts": Array [ + "http://localhost:9200", + ], + "ignoreVersionMismatch": false, + "logQueries": false, + "password": undefined, + "pingTimeout": "PT30S", + "requestHeadersWhitelist": Array [ + "authorization", + ], + "requestTimeout": "PT30S", + "shardTimeout": "PT30S", + "sniffInterval": false, + "sniffOnConnectionFault": false, + "sniffOnStart": false, + "ssl": Object { + "alwaysPresentCertificate": true, + "certificateAuthorities": undefined, + "verificationMode": "full", + }, + "username": undefined, + } + `); }); test('#hosts accepts both string and array of strings', () => { diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index fb585a8d67262..947a0d27546b1 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -65,6 +65,7 @@ export const config = { }), apiVersion: schema.string({ defaultValue: DEFAULT_API_VERSION }), healthCheck: schema.object({ delay: schema.duration({ defaultValue: 2500 }) }), + ignoreVersionMismatch: schema.boolean({ defaultValue: false }), }), }; @@ -74,6 +75,11 @@ export class ElasticsearchConfig { */ public readonly healthCheckDelay: Duration; + /** + * Whether to allow kibana to connect to a non-compatible elasticsearch node. + */ + public readonly ignoreVersionMismatch: boolean; + /** * Version of the Elasticsearch (6.7, 7.1 or `master`) client will be connecting to. */ @@ -161,6 +167,7 @@ export class ElasticsearchConfig { public readonly customHeaders: ElasticsearchConfigType['customHeaders']; constructor(rawConfig: ElasticsearchConfigType) { + this.ignoreVersionMismatch = rawConfig.ignoreVersionMismatch; this.apiVersion = rawConfig.apiVersion; this.logQueries = rawConfig.logQueries; this.hosts = Array.isArray(rawConfig.hosts) ? rawConfig.hosts : [rawConfig.hosts]; diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 9941bbd03f5dc..6525590ee96c5 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -1377,7 +1377,7 @@ describe('SavedObjectsRepository', () => { }; await expect(savedObjectsRepository.find(findOpts)).rejects.toMatchInlineSnapshot(` - [Error: KQLSyntaxError: Expected "(", value, whitespace but "<" found. + [Error: KQLSyntaxError: Expected "(", "{", value, whitespace but "<" found. dashboard.attributes.otherField:< --------------------------------^: Bad Request] `); diff --git a/src/fixtures/logstash_fields.js b/src/fixtures/logstash_fields.js index f3fafc98a5bf4..ab96b69851b71 100644 --- a/src/fixtures/logstash_fields.js +++ b/src/fixtures/logstash_fields.js @@ -25,7 +25,7 @@ function stubbedLogstashFields() { return [ // |aggregatable // | |searchable - // name esType | | |metadata | parent | subType + // name esType | | |metadata | subType ['bytes', 'long', true, true, { count: 10 } ], ['ssl', 'boolean', true, true, { count: 20 } ], ['@timestamp', 'date', true, true, { count: 30 } ], @@ -40,9 +40,9 @@ function stubbedLogstashFields() { ['hashed', 'murmur3', false, true ], ['geo.coordinates', 'geo_point', true, true ], ['extension', 'text', true, true], - ['extension.keyword', 'keyword', true, true, {}, 'extension', 'multi' ], + ['extension.keyword', 'keyword', true, true, {}, { multi: { parent: 'extension' } } ], ['machine.os', 'text', true, true ], - ['machine.os.raw', 'keyword', true, true, {}, 'machine.os', 'multi' ], + ['machine.os.raw', 'keyword', true, true, {}, { multi: { parent: 'machine.os' } } ], ['geo.src', 'keyword', true, true ], ['_id', '_id', true, true ], ['_type', '_type', true, true ], @@ -61,7 +61,6 @@ function stubbedLogstashFields() { aggregatable, searchable, metadata = {}, - parent = undefined, subType = undefined, ] = row; @@ -87,7 +86,6 @@ function stubbedLogstashFields() { script, lang, scripted, - parent, subType, }; }); diff --git a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/types.ts b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/types.ts index fe6f4bcdafec9..f729573b01dc9 100644 --- a/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/types.ts +++ b/src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public/lib/types.ts @@ -61,6 +61,11 @@ export interface SavedObjectMetaData { showSavedObject?(savedObject: SimpleSavedObject): boolean; } +interface FieldSubType { + multi?: { parent: string }; + nested?: { path: string }; +} + export interface Field { name: string; type: string; @@ -70,6 +75,5 @@ export interface Field { aggregatable: boolean; filterable: boolean; searchable: boolean; - parent?: string; - subType?: string; + subType?: FieldSubType; } diff --git a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts index a39175077d9e0..5db3d779d12fa 100644 --- a/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts +++ b/src/legacy/core_plugins/data/public/filter/action/apply_filter_action.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { Filter } from '@kbn/es-query'; -import { npStart } from 'ui/new_platform'; +import { CoreStart } from 'src/core/public'; import { IAction, createAction, @@ -41,6 +41,7 @@ async function isCompatible(context: ActionContext) { } export function createFilterAction( + overlays: CoreStart['overlays'], filterManager: FilterManager, timeFilter: TimefilterContract, indexPatternsService: IndexPatternsStart @@ -73,7 +74,7 @@ export function createFilterAction( ); const filterSelectionPromise: Promise = new Promise(resolve => { - const overlay = npStart.core.overlays.openModal( + const overlay = overlays.openModal( applyFiltersPopover( filters, indexPatterns, diff --git a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts b/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts index 4097f5429a80f..dc5023795bf19 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/fields/field.ts @@ -30,6 +30,11 @@ import { getNotifications } from '../services'; import { getKbnFieldType } from '../../../../../../plugins/data/public'; +interface FieldSubType { + multi?: { parent: string }; + nested?: { path: string }; +} + export type FieldSpec = Record; export interface FieldType { name: string; @@ -47,8 +52,7 @@ export interface FieldType { visualizable?: boolean; readFromDocValues?: boolean; scripted?: boolean; - parent?: string; - subType?: string; + subType?: FieldSubType; displayName?: string; format?: any; } @@ -68,8 +72,7 @@ export class Field implements FieldType { sortable?: boolean; visualizable?: boolean; scripted?: boolean; - parent?: string; - subType?: string; + subType?: FieldSubType; displayName?: string; format: any; routes: Record = { @@ -165,7 +168,6 @@ export class Field implements FieldType { obj.writ('conflictDescriptions'); // multi info - obj.fact('parent'); obj.fact('subType'); return obj.create(); diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 597ad86d39d85..03c9b0e93309d 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -123,6 +123,7 @@ export class DataPlugin implements Plugin - - - - + + @@ -1463,7 +1463,7 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 } } > - - - - + + @@ -2574,7 +2574,7 @@ exports[`QueryBarInput Should render the given query 1`] = ` } } > - - - - + + diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx index d73e741b6d5cb..bfa29bef63462 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx @@ -21,11 +21,20 @@ import { Component } from 'react'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFieldText, EuiOutsideClickDetector, PopoverAnchorPosition } from '@elastic/eui'; - -import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; +import { + EuiFieldText, + EuiOutsideClickDetector, + PopoverAnchorPosition, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiLink, +} from '@elastic/eui'; + +import { InjectedIntl, injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { debounce, compact, isEqual } from 'lodash'; - +import { documentationLinks } from 'ui/documentation_links'; +import { Toast } from 'src/core/public'; import { AutocompleteSuggestion, AutocompleteSuggestionType, @@ -302,20 +311,13 @@ export class QueryBarInputUI extends Component { } }; - private selectSuggestion = ({ - type, - text, - start, - end, - }: { - type: AutocompleteSuggestionType; - text: string; - start: number; - end: number; - }) => { + private selectSuggestion = (suggestion: AutocompleteSuggestion) => { if (!this.inputRef) { return; } + const { type, text, start, end, cursorIndex } = suggestion; + + this.handleNestedFieldSyntaxNotification(suggestion); const query = this.getQueryString(); const { selectionStart, selectionEnd } = this.inputRef; @@ -328,12 +330,75 @@ export class QueryBarInputUI extends Component { this.onQueryStringChange(newQueryString); + this.setState({ + selectionStart: start + (cursorIndex ? cursorIndex : text.length), + selectionEnd: start + (cursorIndex ? cursorIndex : text.length), + }); + if (type === recentSearchType) { this.setState({ isSuggestionsVisible: false, index: null }); this.onSubmit({ query: newQueryString, language: this.props.query.language }); } }; + private handleNestedFieldSyntaxNotification = (suggestion: AutocompleteSuggestion) => { + if ( + 'field' in suggestion && + suggestion.field.subType && + suggestion.field.subType.nested && + !this.services.storage.get('kibana.KQLNestedQuerySyntaxInfoOptOut') + ) { + const notifications = this.services.notifications; + + const onKQLNestedQuerySyntaxInfoOptOut = (toast: Toast) => { + if (!this.services.storage) return; + this.services.storage.set('kibana.KQLNestedQuerySyntaxInfoOptOut', true); + notifications!.toasts.remove(toast); + }; + + if (notifications) { + const toast = notifications.toasts.add({ + title: this.props.intl.formatMessage({ + id: 'data.query.queryBar.KQLNestedQuerySyntaxInfoTitle', + defaultMessage: 'KQL nested query syntax', + }), + text: ( +
+

+ + + + ), + }} + /> +

+ + + onKQLNestedQuerySyntaxInfoOptOut(toast)}> + + + + +
+ ), + }); + } + } + }; + private increaseLimit = () => { this.setState({ suggestionLimit: this.state.suggestionLimit + 50, diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx index 26ee2d80ebf65..d2953621d86d1 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx @@ -21,7 +21,7 @@ import dateMath from '@elastic/datemath'; import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query'; import classNames from 'classnames'; -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { EuiButton, @@ -78,12 +78,10 @@ function QueryBarTopRowUI(props: Props) { const kueryQuerySyntaxLink: string = docLinks!.links.query.kueryQuerySyntax; const queryLanguage = props.query && props.query.language; - let persistedLog: PersistedLog | undefined; - - useEffect(() => { - if (!props.query) return; - persistedLog = getQueryLog(uiSettings!, storage, appName, props.query.language); - }, [queryLanguage]); + const persistedLog: PersistedLog | undefined = React.useMemo( + () => (queryLanguage ? getQueryLog(uiSettings!, storage, appName, queryLanguage) : undefined), + [queryLanguage] + ); function onClickSubmitButton(event: React.MouseEvent) { if (persistedLog && props.query) { diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx index 6fd5274d09ec6..7a9786f5f9ce8 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx @@ -83,7 +83,7 @@ export const SaveQueryForm: FunctionComponent = ({ setSavedQueries(sortedAllSavedQueries); }; fetchQueries(); - }, []); + }, [savedQueryService]); const savedQueryDescriptionText = i18n.translate( 'data.search.searchBar.savedQueryDescriptionText', diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx index b2e47e8b4e850..56116e155eb2f 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx @@ -75,7 +75,7 @@ export const SavedQueryManagementComponent: FunctionComponent = ({ if (isOpen) { fetchCountAndSavedQueries(); } - }, [isOpen, activePage]); + }, [isOpen, activePage, savedQueryService]); const goToPage = (pageNumber: number) => { setActivePage(pageNumber); diff --git a/src/legacy/core_plugins/elasticsearch/index.js b/src/legacy/core_plugins/elasticsearch/index.js index ed6e2d7f7f1c8..f92f920d1afb7 100644 --- a/src/legacy/core_plugins/elasticsearch/index.js +++ b/src/legacy/core_plugins/elasticsearch/index.js @@ -98,10 +98,14 @@ export default function (kibana) { createProxy(server); // Set up the health check service and start it. - const { start, waitUntilReady } = healthCheck(this, server, esConfig.healthCheckDelay.asMilliseconds()); + const { start, waitUntilReady } = healthCheck( + this, + server, + esConfig.healthCheckDelay.asMilliseconds(), + esConfig.ignoreVersionMismatch + ); server.expose('waitUntilReady', waitUntilReady); start(); - } + }, }); - } diff --git a/src/legacy/core_plugins/elasticsearch/lib/__tests__/ensure_es_version.js b/src/legacy/core_plugins/elasticsearch/lib/__tests__/ensure_es_version.js index baf728262aad1..a31c7830f8d5c 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/__tests__/ensure_es_version.js +++ b/src/legacy/core_plugins/elasticsearch/lib/__tests__/ensure_es_version.js @@ -105,6 +105,51 @@ describe('plugins/elasticsearch', () => { } }); + it('does not throw on outdated nodes, if `ignoreVersionMismatch` is enabled in development mode', async () => { + // set config values + server.config = () => ({ + get: name => { + switch (name) { + case 'env.dev': + return true; + default: + throw new Error(`Unknown option "${name}"`); + } + }, + }); + + // 5.0.0 ES is too old to work with a 5.1.0 version of Kibana. + setNodes('5.1.0', '5.2.0', '5.0.0'); + + const ignoreVersionMismatch = true; + const result = await ensureEsVersion(server, KIBANA_VERSION, ignoreVersionMismatch); + expect(result).to.be(true); + }); + + it('throws an error if `ignoreVersionMismatch` is enabled in production mode', async () => { + // set config values + server.config = () => ({ + get: name => { + switch (name) { + case 'env.dev': + return false; + default: + throw new Error(`Unknown option "${name}"`); + } + }, + }); + + // 5.0.0 ES is too old to work with a 5.1.0 version of Kibana. + setNodes('5.1.0', '5.2.0', '5.0.0'); + + try { + const ignoreVersionMismatch = true; + await ensureEsVersion(server, KIBANA_VERSION, ignoreVersionMismatch); + } catch (e) { + expect(e).to.be.a(Error); + } + }); + it('fails if that single node is a client node', async () => { setNodes( '5.1.0', diff --git a/src/legacy/core_plugins/elasticsearch/lib/ensure_es_version.js b/src/legacy/core_plugins/elasticsearch/lib/ensure_es_version.js index 95c7db36d5fb4..cb3ed1886b4a1 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/ensure_es_version.js +++ b/src/legacy/core_plugins/elasticsearch/lib/ensure_es_version.js @@ -36,7 +36,7 @@ import isEsCompatibleWithKibana from './is_es_compatible_with_kibana'; */ const lastWarnedNodesForServer = new WeakMap(); -export function ensureEsVersion(server, kibanaVersion) { +export function ensureEsVersion(server, kibanaVersion, ignoreVersionMismatch = false) { const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('admin'); server.logWithMetadata(['plugin', 'debug'], 'Checking Elasticsearch version'); @@ -102,7 +102,7 @@ export function ensureEsVersion(server, kibanaVersion) { } } - if (incompatibleNodes.length) { + if (incompatibleNodes.length && !shouldIgnoreVersionMismatch(server, ignoreVersionMismatch)) { const incompatibleNodeNames = getHumanizedNodeNames(incompatibleNodes); throw new Error( `This version of Kibana requires Elasticsearch v` + @@ -114,3 +114,12 @@ export function ensureEsVersion(server, kibanaVersion) { return true; }); } + +function shouldIgnoreVersionMismatch(server, ignoreVersionMismatch) { + const isDevMode = server.config().get('env.dev'); + if(!isDevMode && ignoreVersionMismatch) { + throw new Error(`Option "elasticsearch.ignoreVersionMismatch" can only be used in development mode`); + } + + return isDevMode && ignoreVersionMismatch; +} diff --git a/src/legacy/core_plugins/elasticsearch/lib/health_check.js b/src/legacy/core_plugins/elasticsearch/lib/health_check.js index 1b05d51b02494..ac8e6caab9496 100644 --- a/src/legacy/core_plugins/elasticsearch/lib/health_check.js +++ b/src/legacy/core_plugins/elasticsearch/lib/health_check.js @@ -21,7 +21,7 @@ import Bluebird from 'bluebird'; import kibanaVersion from './kibana_version'; import { ensureEsVersion } from './ensure_es_version'; -export default function (plugin, server, requestDelay) { +export default function (plugin, server, requestDelay, ignoreVersionMismatch) { plugin.status.yellow('Waiting for Elasticsearch'); function waitUntilReady() { @@ -31,7 +31,7 @@ export default function (plugin, server, requestDelay) { } function check() { - return ensureEsVersion(server, kibanaVersion.get()) + return ensureEsVersion(server, kibanaVersion.get(), ignoreVersionMismatch) .then(() => plugin.status.green('Ready')) .catch(err => plugin.status.red(err)); } diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx index 8359663610f29..c7635017caef2 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/expression_renderer.tsx @@ -57,6 +57,7 @@ export const ExpressionRendererImplementation = ({ const [state, setState] = useState({ ...defaultState }); // Re-fetch data automatically when the inputs change + /* eslint-disable react-hooks/exhaustive-deps */ useEffect(() => { if (handlerRef.current) { handlerRef.current.update(expression, options); @@ -68,6 +69,7 @@ export const ExpressionRendererImplementation = ({ options.variables, options.disableCaching, ]); + /* eslint-enable react-hooks/exhaustive-deps */ // Initialize the loader only once useEffect(() => { diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts index 36917bf4e8384..a3caa1c47b150 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/loader.test.ts @@ -23,7 +23,7 @@ import { ExpressionDataHandler } from './execute'; import { fromExpression } from '@kbn/interpreter/common'; import { IInterpreterRenderHandlers } from './types'; import { Observable } from 'rxjs'; -import { ExpressionAST } from '../../../../../../plugins/expressions/common'; +import { ExpressionAST } from '../../../../../../plugins/expressions/public'; const element: HTMLElement = null as any; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx b/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx index 1a2f473f4c9a1..b17cd454b65ea 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/mocks.tsx @@ -39,6 +39,11 @@ const createExpressionsSetupMock = (): ExpressionsSetup => { types: { register: () => {}, } as any, + getExecutor: () => ({ + interpreter: { + interpretAst: () => {}, + }, + }), }, }; }; diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts index ec70248694990..d2c6c14c17de1 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/plugin.ts @@ -19,9 +19,10 @@ /* eslint-disable */ import { npSetup } from 'ui/new_platform'; -import { ExpressionsSetupContract } from '../../../../../../plugins/expressions/public/expressions/expressions_service'; /* eslint-enable */ +import { ExpressionsSetup } from '../../../../../../plugins/expressions/public'; + import { CoreSetup, CoreStart, @@ -32,7 +33,7 @@ import { Start as InspectorStart, Setup as InspectorSetup, } from '../../../../../../plugins/inspector/public'; -import { IInterpreter } from './types'; +import { ExpressionInterpreter } from './types'; import { setInterpreter, setInspector, setRenderersRegistry } from './services'; import { ExpressionRendererImplementation } from './expression_renderer'; import { ExpressionLoader, loader } from './loader'; @@ -47,7 +48,7 @@ export interface ExpressionsStartDeps { inspector: InspectorStart; } -export type ExpressionsSetup = ExpressionsSetupContract; +export { ExpressionsSetup }; export type ExpressionsStart = ReturnType; export class ExpressionsPublicPlugin @@ -61,7 +62,7 @@ export class ExpressionsPublicPlugin // eslint-disable-next-line const { getInterpreter } = require('../../../../interpreter/public/interpreter'); getInterpreter() - .then(({ interpreter }: { interpreter: IInterpreter }) => { + .then(({ interpreter }: { interpreter: ExpressionInterpreter }) => { setInterpreter(interpreter); }) .catch((e: Error) => { diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts index f67b4c2d8e272..09a310d381c9e 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/render.ts @@ -104,7 +104,7 @@ export class ExpressionRenderHandler { try { // Rendering is asynchronous, completed by handlers.done() getRenderersRegistry() - .get(data.as) + .get(data.as)! .render(this.element, data.value, { ...this.handlers, ...extraHandlers }); } catch (e) { this.renderSubject.next({ diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts index ae2a7955233d1..5c357b5dcd2bb 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/services.ts @@ -17,13 +17,15 @@ * under the License. */ +import { ExpressionInterpreter } from './types'; import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; -import { IInterpreter } from './types'; import { Start as IInspector } from '../../../../../../plugins/inspector/public'; import { ExpressionsSetup } from './plugin'; export const [getInspector, setInspector] = createGetterSetter('Inspector'); -export const [getInterpreter, setInterpreter] = createGetterSetter('Interpreter'); +export const [getInterpreter, setInterpreter] = createGetterSetter( + 'Interpreter' +); export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter< ExpressionsSetup['__LEGACY']['renderers'] >('Renderers registry'); diff --git a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts b/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts index 0390440298c55..a8d89b4456693 100644 --- a/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts +++ b/src/legacy/core_plugins/expressions/public/np_ready/public/types.ts @@ -21,76 +21,6 @@ import { Filter } from '@kbn/es-query'; import { TimeRange } from '../../../../../../plugins/data/public'; import { Adapters } from '../../../../../../plugins/inspector/public'; import { Query } from '../../../../../../plugins/data/public'; -import { ExpressionAST } from '../../../../../../plugins/expressions/common'; -export { ExpressionAST, TimeRange, Adapters, Filter, Query }; - -export type RenderId = number; -export type Data = any; -export type event = any; -export type Context = object; - -export interface SearchContext { - type: 'kibana_context'; - filters?: Filter[]; - query?: Query; - timeRange?: TimeRange; -} - -export type IGetInitialContext = () => SearchContext | Context; - -export interface IExpressionLoaderParams { - searchContext?: SearchContext; - context?: Context; - variables?: Record; - disableCaching?: boolean; - customFunctions?: []; - customRenderers?: []; - extraHandlers?: Record; -} - -export interface IInterpreterHandlers { - getInitialContext: IGetInitialContext; - inspectorAdapters?: Adapters; - abortSignal?: AbortSignal; -} - -export interface IInterpreterErrorResult { - type: 'error'; - error: { message: string; name: string; stack: string }; -} - -export interface IInterpreterSuccessResult { - type: string; - as?: string; - value?: unknown; - error?: unknown; -} - -export type IInterpreterResult = IInterpreterSuccessResult & IInterpreterErrorResult; - -export interface IInterpreterRenderHandlers { - // Done increments the number of rendering successes - done: () => void; - onDestroy: (fn: () => void) => void; - reload: () => void; - update: (params: any) => void; - event: (event: event) => void; -} - -export interface IInterpreterRenderFunction { - name: string; - displayName: string; - help: string; - validate: () => void; - reuseDomNode: boolean; - render: (domNode: Element, data: T, handlers: IInterpreterRenderHandlers) => void | Promise; -} - -export interface IInterpreter { - interpretAst( - ast: ExpressionAST, - context: Context, - handlers: IInterpreterHandlers - ): Promise; -} +export { TimeRange, Adapters, Filter, Query }; +export * from '../../../../../../plugins/expressions/public'; diff --git a/src/legacy/core_plugins/interpreter/common/index.ts b/src/legacy/core_plugins/interpreter/common/index.ts index 0251faa69fccf..8a54741e9d3e1 100644 --- a/src/legacy/core_plugins/interpreter/common/index.ts +++ b/src/legacy/core_plugins/interpreter/common/index.ts @@ -36,7 +36,8 @@ export { PointSeriesColumn, PointSeriesColumnName, Render, + ExpressionTypeStyle, Style, Type, -} from '../../../../plugins/expressions/common'; +} from '../../../../plugins/expressions/public'; export const API_ROUTE = '/api/interpreter'; diff --git a/src/legacy/core_plugins/interpreter/common/lib/fonts.ts b/src/legacy/core_plugins/interpreter/common/lib/fonts.ts index cdf3d4c16f3b5..1594f42abf2eb 100644 --- a/src/legacy/core_plugins/interpreter/common/lib/fonts.ts +++ b/src/legacy/core_plugins/interpreter/common/lib/fonts.ts @@ -17,135 +17,5 @@ * under the License. */ -/** - * This type contains a unions of all supported font labels, or the the name of - * the font the user would see in a UI. - */ -export type FontLabel = typeof fonts[number]['label']; - -/** - * This type contains a union of all supported font values, equivalent to the CSS - * `font-value` property. - */ -export type FontValue = typeof fonts[number]['value']; - -/** - * An interface representing a font in Canvas, with a textual label and the CSS - * `font-value`. - */ -export interface Font { - label: FontLabel; - value: FontValue; -} - -// This function allows one to create a strongly-typed font for inclusion in -// the font collection. As a result, the values and labels are known to the -// type system, preventing one from specifying a non-existent font at build -// time. -function createFont< - RawFont extends { value: RawFontValue; label: RawFontLabel }, - RawFontValue extends string, - RawFontLabel extends string ->(font: RawFont) { - return font; -} - -export const americanTypewriter = createFont({ - label: 'American Typewriter', - value: "'American Typewriter', 'Courier New', Courier, Monaco, mono", -}); - -export const arial = createFont({ label: 'Arial', value: 'Arial, sans-serif' }); - -export const baskerville = createFont({ - label: 'Baskerville', - value: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const bookAntiqua = createFont({ - label: 'Book Antiqua', - value: "'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const brushScript = createFont({ - label: 'Brush Script', - value: "'Brush Script MT', 'Comic Sans', sans-serif", -}); - -export const chalkboard = createFont({ - label: 'Chalkboard', - value: "Chalkboard, 'Comic Sans', sans-serif", -}); - -export const didot = createFont({ - label: 'Didot', - value: "Didot, Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -export const futura = createFont({ - label: 'Futura', - value: 'Futura, Impact, Helvetica, Arial, sans-serif', -}); - -export const gillSans = createFont({ - label: 'Gill Sans', - value: - "'Gill Sans', 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", -}); - -export const helveticaNeue = createFont({ - label: 'Helvetica Neue', - value: "'Helvetica Neue', Helvetica, Arial, sans-serif", -}); - -export const hoeflerText = createFont({ - label: 'Hoefler Text', - value: "'Hoefler Text', Garamond, Georgia, 'Times New Roman', Times, serif", -}); - -export const lucidaGrande = createFont({ - label: 'Lucida Grande', - value: "'Lucida Grande', 'Lucida Sans Unicode', Lucida, Verdana, Helvetica, Arial, sans-serif", -}); - -export const myriad = createFont({ - label: 'Myriad', - value: 'Myriad, Helvetica, Arial, sans-serif', -}); - -export const openSans = createFont({ - label: 'Open Sans', - value: "'Open Sans', Helvetica, Arial, sans-serif", -}); - -export const optima = createFont({ - label: 'Optima', - value: "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", -}); - -export const palatino = createFont({ - label: 'Palatino', - value: "Palatino, 'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", -}); - -/** - * A collection of supported fonts. - */ -export const fonts = [ - americanTypewriter, - arial, - baskerville, - bookAntiqua, - brushScript, - chalkboard, - didot, - futura, - gillSans, - helveticaNeue, - hoeflerText, - lucidaGrande, - myriad, - openSans, - optima, - palatino, -]; +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/fonts'; diff --git a/src/legacy/core_plugins/interpreter/init.ts b/src/legacy/core_plugins/interpreter/init.ts index fc93346f28024..f09ffd4697e74 100644 --- a/src/legacy/core_plugins/interpreter/init.ts +++ b/src/legacy/core_plugins/interpreter/init.ts @@ -25,7 +25,7 @@ import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common // @ts-ignore import { routes } from './server/routes'; -import { typeSpecs as types } from '../../../plugins/expressions/common'; +import { typeSpecs as types } from '../../../plugins/expressions/public'; import { Type } from './common'; import { Legacy } from '../../../../kibana'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/clog.ts b/src/legacy/core_plugins/interpreter/public/functions/clog.ts index 586584b498ac7..65362fe363374 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/clog.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/clog.ts @@ -17,19 +17,5 @@ * under the License. */ -import { ExpressionFunction } from '../../types'; - -const name = 'clog'; - -type Context = any; -type ClogExpressionFunction = ExpressionFunction; - -export const clog = (): ClogExpressionFunction => ({ - name, - args: {}, - help: 'Outputs the context to the console', - fn: context => { - console.log(context); // eslint-disable-line no-console - return context; - }, -}); +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/functions/clog'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts index 6fcfde0a5b06b..d232a97c3c34c 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/esaggs.ts @@ -19,18 +19,12 @@ import { get, has } from 'lodash'; import { i18n } from '@kbn/i18n'; -// @ts-ignore import { AggConfigs } from 'ui/agg_types/agg_configs'; import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import chrome from 'ui/chrome'; - -// need to get rid of angular from these -// @ts-ignore import { TimeRange } from 'src/plugins/data/public'; import { SearchSource } from '../../../../ui/public/courier/search_source'; -// @ts-ignore import { FilterBarQueryFilterProvider } from '../../../../ui/public/filter_manager/query_filter'; - import { buildTabularInspectorData } from '../../../../ui/public/inspector/build_tabular_inspector_data'; import { getRequestInspectorStats, @@ -39,12 +33,15 @@ import { import { calculateObjectHash } from '../../../../ui/public/vis/lib/calculate_object_hash'; import { getTime } from '../../../../ui/public/timefilter'; import { RequestHandlerParams } from '../../../../ui/public/visualize/loader/embedded_visualize_handler'; -// @ts-ignore -import { tabifyAggResponse } from '../../../../ui/public/agg_response/tabify/tabify'; import { KibanaContext, KibanaDatatable } from '../../common'; import { ExpressionFunction, KibanaDatatableColumn } from '../../types'; import { start as data } from '../../../data/public/legacy'; +// @ts-ignore +import { tabifyAggResponse } from '../../../../ui/public/agg_response/tabify/tabify'; +// @ts-ignore +import { SearchSourceProvider } from '../../../../ui/public/courier/search_source'; + const name = 'esaggs'; type Context = KibanaContext | null; diff --git a/src/legacy/core_plugins/interpreter/public/functions/font.ts b/src/legacy/core_plugins/interpreter/public/functions/font.ts index 5cd5add318e26..2f3bc1ff37e2c 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/font.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/font.ts @@ -17,161 +17,5 @@ * under the License. */ -// @ts-ignore no @typed def -import inlineStyle from 'inline-style'; -import { i18n } from '@kbn/i18n'; -import { openSans } from '../../common/lib/fonts'; -import { ExpressionFunction } from '../../types'; -import { - CSSStyle, - FontFamily, - FontStyle, - FontWeight, - Style, - TextAlignment, - TextDecoration, -} from '../types'; - -interface Arguments { - align?: TextAlignment; - color?: string; - family?: FontFamily; - italic?: boolean; - lHeight?: number | null; - size?: number; - underline?: boolean; - weight?: FontWeight; -} - -export function font(): ExpressionFunction<'font', null, Arguments, Style> { - return { - name: 'font', - aliases: [], - type: 'style', - help: i18n.translate('interpreter.functions.fontHelpText', { - defaultMessage: 'Create a font style.', - }), - context: { - types: ['null'], - }, - args: { - align: { - default: 'left', - help: i18n.translate('interpreter.functions.font.args.alignHelpText', { - defaultMessage: 'The horizontal text alignment.', - }), - options: Object.values(TextAlignment), - types: ['string'], - }, - color: { - help: i18n.translate('interpreter.functions.font.args.colorHelpText', { - defaultMessage: 'The text color.', - }), - types: ['string'], - }, - family: { - default: `"${openSans.value}"`, - help: i18n.translate('interpreter.functions.font.args.familyHelpText', { - defaultMessage: 'An acceptable {css} web font string', - values: { - css: 'CSS', - }, - }), - types: ['string'], - }, - italic: { - default: false, - help: i18n.translate('interpreter.functions.font.args.italicHelpText', { - defaultMessage: 'Italicize the text?', - }), - options: [true, false], - types: ['boolean'], - }, - lHeight: { - default: null, - aliases: ['lineHeight'], - help: i18n.translate('interpreter.functions.font.args.lHeightHelpText', { - defaultMessage: 'The line height in pixels', - }), - types: ['number', 'null'], - }, - size: { - default: 14, - help: i18n.translate('interpreter.functions.font.args.sizeHelpText', { - defaultMessage: 'The font size in pixels', - }), - types: ['number'], - }, - underline: { - default: false, - help: i18n.translate('interpreter.functions.font.args.underlineHelpText', { - defaultMessage: 'Underline the text?', - }), - options: [true, false], - types: ['boolean'], - }, - weight: { - default: 'normal', - help: i18n.translate('interpreter.functions.font.args.weightHelpText', { - defaultMessage: 'The font weight. For example, {list}, or {end}.', - values: { - list: Object.values(FontWeight) - .slice(0, -1) - .map(weight => `\`"${weight}"\``) - .join(', '), - end: `\`"${Object.values(FontWeight).slice(-1)[0]}"\``, - }, - }), - options: Object.values(FontWeight), - types: ['string'], - }, - }, - fn: (_context, args) => { - if (!Object.values(FontWeight).includes(args.weight!)) { - throw new Error( - i18n.translate('interpreter.functions.font.invalidFontWeightErrorMessage', { - defaultMessage: "Invalid font weight: '{weight}'", - values: { - weight: args.weight, - }, - }) - ); - } - if (!Object.values(TextAlignment).includes(args.align!)) { - throw new Error( - i18n.translate('interpreter.functions.font.invalidTextAlignmentErrorMessage', { - defaultMessage: "Invalid text alignment: '{align}'", - values: { - align: args.align, - }, - }) - ); - } - - // the line height shouldn't ever be lower than the size, and apply as a - // pixel setting - const lineHeight = args.lHeight != null ? `${args.lHeight}px` : '1'; - - const spec: CSSStyle = { - fontFamily: args.family, - fontWeight: args.weight, - fontStyle: args.italic ? FontStyle.ITALIC : FontStyle.NORMAL, - textDecoration: args.underline ? TextDecoration.UNDERLINE : TextDecoration.NONE, - textAlign: args.align, - fontSize: `${args.size}px`, // apply font size as a pixel setting - lineHeight, // apply line height as a pixel setting - }; - - // conditionally apply styles based on input - if (args.color) { - spec.color = args.color; - } - - return { - type: 'style', - spec, - css: inlineStyle(spec), - }; - }, - }; -} +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/functions/font'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana.ts b/src/legacy/core_plugins/interpreter/public/functions/kibana.ts index c027b220ad0d0..7da284d9672a5 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/kibana.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/kibana.ts @@ -17,47 +17,5 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaContext } from '../../types'; - -export type ExpressionFunctionKibana = ExpressionFunction< - 'kibana', - KibanaContext | null, - object, - KibanaContext ->; - -export const kibana = (): ExpressionFunctionKibana => ({ - name: 'kibana', - type: 'kibana_context', - - context: { - types: ['kibana_context', 'null'], - }, - - help: i18n.translate('interpreter.functions.kibana.help', { - defaultMessage: 'Gets kibana global context', - }), - args: {}, - fn(context, args, handlers) { - const initialContext = handlers.getInitialContext ? handlers.getInitialContext() : {}; - - if (context && context.query) { - initialContext.query = initialContext.query.concat(context.query); - } - - if (context && context.filters) { - initialContext.filters = initialContext.filters.concat(context.filters); - } - - const timeRange = initialContext.timeRange || (context ? context.timeRange : undefined); - - return { - ...context, - type: 'kibana_context', - query: initialContext.query, - filters: initialContext.filters, - timeRange, - }; - }, -}); +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/functions/kibana'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/range.ts b/src/legacy/core_plugins/interpreter/public/functions/range.ts index 8ae07dd82a22c..322bda983a8ff 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/range.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/range.ts @@ -17,48 +17,5 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { KibanaDatatable, Range } from '../../types'; -import { ExpressionFunction } from '../../types'; - -const name = 'range'; - -type Context = KibanaDatatable | null; - -interface Arguments { - from: number; - to: number; -} - -type Return = Range; // imported from type - -export const range = (): ExpressionFunction => ({ - name, - help: i18n.translate('interpreter.function.range.help', { - defaultMessage: 'Generates range object', - }), - type: 'range', - args: { - from: { - types: ['number'], - help: i18n.translate('interpreter.function.range.from.help', { - defaultMessage: 'Start of range', - }), - required: true, - }, - to: { - types: ['number'], - help: i18n.translate('interpreter.function.range.to.help', { - defaultMessage: 'End of range', - }), - required: true, - }, - }, - fn: (context, args) => { - return { - type: 'range', - from: args.from, - to: args.to, - }; - }, -}); +// eslint-disable-next-line +export * from '../../../../../plugins/visualizations/public/expression_functions/range'; diff --git a/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts b/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts index 4190a597b0120..d4ebeeb5267e1 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts +++ b/src/legacy/core_plugins/interpreter/public/functions/vis_dimension.ts @@ -17,73 +17,5 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaDatatable } from '../../types'; - -const name = 'visdimension'; - -type Context = KibanaDatatable | null; - -interface Arguments { - accessor: string | number; - format?: string; - formatParams?: string; -} - -type Return = any; - -export const visDimension = (): ExpressionFunction => ({ - name: 'visdimension', - help: i18n.translate('interpreter.function.visDimension.help', { - defaultMessage: 'Generates visConfig dimension object', - }), - type: 'vis_dimension', - context: { - types: ['kibana_datatable'], - }, - args: { - accessor: { - types: ['string', 'number'], - aliases: ['_'], - help: i18n.translate('interpreter.function.visDimension.accessor.help', { - defaultMessage: 'Column in your dataset to use (either column index or column name)', - }), - }, - format: { - types: ['string'], - default: 'string', - help: i18n.translate('interpreter.function.visDimension.format.help', { - defaultMessage: 'Format', - }), - }, - formatParams: { - types: ['string'], - default: '"{}"', - help: i18n.translate('interpreter.function.visDimension.formatParams.help', { - defaultMessage: 'Format params', - }), - }, - }, - fn: (context, args) => { - const accessor = - typeof args.accessor === 'number' - ? args.accessor - : context!.columns.find(c => c.id === args.accessor); - if (accessor === undefined) { - throw new Error( - i18n.translate('interpreter.function.visDimension.error.accessor', { - defaultMessage: 'Column name provided is invalid', - }) - ); - } - - return { - type: 'vis_dimension', - accessor, - format: { - id: args.format, - params: JSON.parse(args.formatParams!), - }, - }; - }, -}); +// eslint-disable-next-line +export * from '../../../../../plugins/visualizations/public/expression_functions/vis_dimension'; diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.test.ts b/src/legacy/core_plugins/interpreter/public/interpreter.test.ts deleted file mode 100644 index 429a943c3ff36..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/interpreter.test.ts +++ /dev/null @@ -1,120 +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. - */ - -jest.mock('ui/new_platform', () => ({ - npSetup: { - core: { - http: {}, - injectedMetadata: { - getKibanaVersion: () => '8.0.0', - getBasePath: () => '/lol', - }, - }, - }, -})); -jest.mock('uiExports/interpreter'); - -jest.mock('@kbn/interpreter/common', () => ({ - register: jest.fn(), - registryFactory: jest.fn(), -})); - -const mockExecutor = { - interpreter: { - interpretAst: jest.fn(), - }, -}; -jest.mock('./lib/interpreter', () => ({ - initializeExecutor: jest.fn().mockReturnValue(Promise.resolve(mockExecutor)), -})); - -jest.mock('./registries', () => ({ - registries: { - browserFunctions: jest.fn(), - renderers: jest.fn(), - types: jest.fn(), - }, -})); - -jest.mock('../../../ui/public/new_platform'); -jest.mock('./functions', () => ({ functions: [{}, {}, {}] })); -jest.mock('./renderers/visualization', () => ({ visualization: {} })); - -describe('interpreter/interpreter', () => { - let getInterpreter: any; - let interpretAst: any; - let initializeExecutor: any; - - beforeEach(() => { - jest.clearAllMocks(); - jest.resetModules(); - getInterpreter = require('./interpreter').getInterpreter; - interpretAst = require('./interpreter').interpretAst; - initializeExecutor = require('./lib/interpreter').initializeExecutor; - }); - - describe('getInterpreter', () => { - it('initializes interpreter', async () => { - await getInterpreter(); - expect(initializeExecutor).toHaveBeenCalledTimes(1); - }); - - it('only initializes interpreter once', async () => { - await getInterpreter(); - await getInterpreter(); - expect(initializeExecutor).toHaveBeenCalledTimes(1); - }); - - it('resolves', async () => { - await expect(getInterpreter()).resolves; - }); - - it('resolves with interpreter object', async () => { - const interpreter = await getInterpreter(); - await expect(Object.keys(interpreter)).toEqual(['interpreter']); - }); - }); - - describe('interpretAst', () => { - it('resolves', async () => { - const params = [{}]; - await expect(interpretAst(...params)).resolves; - }); - - it('initializes interpreter if needed', async () => { - const params = [{}]; - await interpretAst(...params); - expect(initializeExecutor).toHaveBeenCalledTimes(1); - }); - - it('calls interpreter.interpretAst with the provided params', async () => { - const params = [{}]; - await interpretAst(...params); - expect(mockExecutor.interpreter.interpretAst).toHaveBeenCalledTimes(1); - expect(mockExecutor.interpreter.interpretAst).toHaveBeenCalledWith({}, undefined, undefined); - }); - - it('calls interpreter.interpretAst each time', async () => { - const params = [{}]; - await interpretAst(...params); - await interpretAst(...params); - expect(mockExecutor.interpreter.interpretAst).toHaveBeenCalledTimes(2); - }); - }); -}); diff --git a/src/legacy/core_plugins/interpreter/public/interpreter.ts b/src/legacy/core_plugins/interpreter/public/interpreter.ts index 1f0f8141345d2..a337f7e4ebfea 100644 --- a/src/legacy/core_plugins/interpreter/public/interpreter.ts +++ b/src/legacy/core_plugins/interpreter/public/interpreter.ts @@ -20,38 +20,42 @@ import 'uiExports/interpreter'; // @ts-ignore import { register, registryFactory } from '@kbn/interpreter/common'; -import { - initializeExecutor, - ExpressionExecutor, - ExpressionInterpretWithHandlers, -} from './lib/interpreter'; +import { npSetup } from 'ui/new_platform'; import { registries } from './registries'; -import { functions } from './functions'; import { visualization } from './renderers/visualization'; -import { typeSpecs } from '../../../../plugins/expressions/common'; +import { + ExpressionInterpretWithHandlers, + ExpressionExecutor, +} from '../../../../plugins/expressions/public'; +import { esaggs as esaggsFn } from './functions/esaggs'; +import { visualization as visualizationFn } from './functions/visualization'; // Expose kbnInterpreter.register(specs) and kbnInterpreter.registries() globally so that plugins // can register without a transpile step. +// TODO: This will be left behind in then legacy platform? (global as any).kbnInterpreter = Object.assign( (global as any).kbnInterpreter || {}, registryFactory(registries) ); -register(registries, { - types: typeSpecs, - browserFunctions: functions, - renderers: [visualization], -}); +// TODO: This needs to be moved to `data` plugin Search service. +registries.browserFunctions.register(esaggsFn); -let executorPromise: Promise | undefined; +// TODO: This needs to be moved to `visualizations` plugin. +registries.browserFunctions.register(visualizationFn); +registries.renderers.register(visualization); +// TODO: This function will be left behind in the legacy platform. +let executorPromise: Promise | undefined; export const getInterpreter = async () => { if (!executorPromise) { - executorPromise = initializeExecutor(); + const executor = npSetup.plugins.expressions.__LEGACY.getExecutor(); + executorPromise = Promise.resolve(executor); } return await executorPromise; }; +// TODO: This function will be left behind in the legacy platform. export const interpretAst: ExpressionInterpretWithHandlers = async (ast, context, handlers) => { const { interpreter } = await getInterpreter(); return await interpreter.interpretAst(ast, context, handlers); diff --git a/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts b/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts index 46e85411c5895..c14272fbf8def 100644 --- a/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts +++ b/src/legacy/core_plugins/interpreter/public/lib/create_handlers.ts @@ -17,8 +17,5 @@ * under the License. */ -export function createHandlers() { - return { - environment: 'client', - }; -} +// eslint-disable-next-line +export * from '../../../../../plugins/expressions/public/create_handlers'; diff --git a/src/legacy/core_plugins/interpreter/public/lib/interpreter.ts b/src/legacy/core_plugins/interpreter/public/lib/interpreter.ts deleted file mode 100644 index 399ecc5950268..0000000000000 --- a/src/legacy/core_plugins/interpreter/public/lib/interpreter.ts +++ /dev/null @@ -1,51 +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 { ExpressionInterpret } from 'src/plugins/expressions/common/expressions/interpreter_provider'; -import { interpreterProvider } from '../../common'; -import { createHandlers } from './create_handlers'; -import { registries } from '../registries'; -import { FunctionHandlers } from '../../types'; - -export type ExpressionInterpretWithHandlers = ( - ast: Parameters[0], - context: Parameters[1], - handlers: FunctionHandlers -) => ReturnType; - -export interface ExpressionInterpreter { - interpretAst: ExpressionInterpretWithHandlers; -} - -export interface ExpressionExecutor { - interpreter: ExpressionInterpreter; -} - -export async function initializeExecutor(): Promise { - const interpretAst: ExpressionInterpretWithHandlers = async (ast, context, handlers) => { - const interpret = await interpreterProvider({ - types: registries.types.toJS(), - handlers: { ...handlers, ...createHandlers() }, - functions: registries.browserFunctions.toJS(), - }); - return interpret(ast, context); - }; - - return { interpreter: { interpretAst } }; -} diff --git a/src/legacy/core_plugins/interpreter/public/types/style.ts b/src/legacy/core_plugins/interpreter/public/types/style.ts index fd22b9bae2e95..7386048a4ae04 100644 --- a/src/legacy/core_plugins/interpreter/public/types/style.ts +++ b/src/legacy/core_plugins/interpreter/public/types/style.ts @@ -17,120 +17,10 @@ * under the License. */ -import { FontLabel } from '../../common/lib/fonts'; -export { FontLabel as FontFamily, FontValue } from '../../common/lib/fonts'; - -/** - * Enum of supported CSS `background-repeat` properties. - */ -export enum BackgroundRepeat { - REPEAT = 'repeat', - REPEAT_NO = 'no-repeat', - REPEAT_X = 'repeat-x', - REPEAT_Y = 'repeat-y', - ROUND = 'round', - SPACE = 'space', -} - -/** - * Enum of supported CSS `background-size` properties. - */ -export enum BackgroundSize { - AUTO = 'auto', - CONTAIN = 'contain', - COVER = 'cover', -} - -/** - * Enum of supported CSS `font-style` properties. - */ -export enum FontStyle { - ITALIC = 'italic', - NORMAL = 'normal', -} - -/** - * Enum of supported CSS `font-weight` properties. - */ -export enum FontWeight { - NORMAL = 'normal', - BOLD = 'bold', - BOLDER = 'bolder', - LIGHTER = 'lighter', - ONE = '100', - TWO = '200', - THREE = '300', - FOUR = '400', - FIVE = '500', - SIX = '600', - SEVEN = '700', - EIGHT = '800', - NINE = '900', -} - -/** - * Enum of supported CSS `overflow` properties. - */ -export enum Overflow { - AUTO = 'auto', - HIDDEN = 'hidden', - SCROLL = 'scroll', - VISIBLE = 'visible', -} - -/** - * Enum of supported CSS `text-align` properties. - */ -export enum TextAlignment { - CENTER = 'center', - JUSTIFY = 'justify', - LEFT = 'left', - RIGHT = 'right', -} - -/** - * Enum of supported CSS `text-decoration` properties. - */ -export enum TextDecoration { - NONE = 'none', - UNDERLINE = 'underline', -} - -/** - * Represents the various style properties that can be applied to an element. - */ -export interface CSSStyle { - color?: string; - fill?: string; - fontFamily?: FontLabel; - fontSize?: string; - fontStyle?: FontStyle; - fontWeight?: FontWeight; - lineHeight?: number | string; - textAlign?: TextAlignment; - textDecoration?: TextDecoration; -} - -/** - * Represents an object containing style information for a Container. - */ -export interface ContainerStyle { - border: string | null; - borderRadius: string | null; - padding: string | null; - backgroundColor: string | null; - backgroundImage: string | null; - backgroundSize: BackgroundSize; - backgroundRepeat: BackgroundRepeat; - opacity: number | null; - overflow: Overflow; -} - -/** - * An object that represents style information, typically CSS. - */ -export interface Style { - type: 'style'; - spec: CSSStyle; - css: string; -} +/* eslint-disable */ +export * from '../../../../../plugins/expressions/public/types/style'; +export { + FontLabel as FontFamily, + FontValue, +} from '../../../../../plugins/expressions/public/fonts'; +/* eslint-enable */ diff --git a/src/legacy/core_plugins/interpreter/test_helpers.ts b/src/legacy/core_plugins/interpreter/test_helpers.ts index 1f39a8271367c..0e34f42b01544 100644 --- a/src/legacy/core_plugins/interpreter/test_helpers.ts +++ b/src/legacy/core_plugins/interpreter/test_helpers.ts @@ -17,17 +17,5 @@ * under the License. */ -import { mapValues } from 'lodash'; -import { AnyExpressionFunction, FunctionHandlers } from './types'; - -// Takes a function spec and passes in default args, -// overriding with any provided args. -export const functionWrapper = (fnSpec: () => T) => { - const spec = fnSpec(); - const defaultArgs = mapValues(spec.args, argSpec => argSpec.default); - return ( - context: object | null, - args: Record = {}, - handlers: FunctionHandlers = {} - ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); -}; +// eslint-disable-next-line +export * from '../../../plugins/expressions/public/functions/tests/utils'; diff --git a/src/legacy/core_plugins/interpreter/types/arguments.ts b/src/legacy/core_plugins/interpreter/types/arguments.ts index 3e17166a7c434..35566381d010d 100644 --- a/src/legacy/core_plugins/interpreter/types/arguments.ts +++ b/src/legacy/core_plugins/interpreter/types/arguments.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from '../../../../plugins/expressions/common/expressions/types/arguments'; +// eslint-disable-next-line +export * from '../../../../plugins/expressions/public/types/arguments'; diff --git a/src/legacy/core_plugins/interpreter/types/common.ts b/src/legacy/core_plugins/interpreter/types/common.ts index dce984a0bd556..99a7d2dc92f06 100644 --- a/src/legacy/core_plugins/interpreter/types/common.ts +++ b/src/legacy/core_plugins/interpreter/types/common.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from '../../../../plugins/expressions/common/expressions/types/common'; +// eslint-disable-next-line +export * from '../../../../plugins/expressions/public/types/common'; diff --git a/src/legacy/core_plugins/interpreter/types/functions.ts b/src/legacy/core_plugins/interpreter/types/functions.ts index 789bb29990cb4..9a99a78281a0c 100644 --- a/src/legacy/core_plugins/interpreter/types/functions.ts +++ b/src/legacy/core_plugins/interpreter/types/functions.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from '../../../../plugins/expressions/common/expressions/types/functions'; +// eslint-disable-next-line +export * from '../../../../plugins/expressions/public/types/functions'; diff --git a/src/legacy/core_plugins/interpreter/types/index.ts b/src/legacy/core_plugins/interpreter/types/index.ts index c178bee26a7cd..6cefc47a678d4 100644 --- a/src/legacy/core_plugins/interpreter/types/index.ts +++ b/src/legacy/core_plugins/interpreter/types/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from '../../../../plugins/expressions/common'; +export * from '../../../../plugins/expressions/public'; diff --git a/src/legacy/core_plugins/interpreter/types/types.ts b/src/legacy/core_plugins/interpreter/types/types.ts index d3f1cb3987b00..aacb63eaace0c 100644 --- a/src/legacy/core_plugins/interpreter/types/types.ts +++ b/src/legacy/core_plugins/interpreter/types/types.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from '../../../../plugins/expressions/common/expressions/types'; +// eslint-disable-next-line +export * from '../../../../plugins/expressions/public/types'; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx index 282c2e6a356bf..b614e00ba8cd0 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/number_input.tsx @@ -34,6 +34,12 @@ interface NumberInputOptionProps { setValue: (paramName: ParamName, value: number | '') => void; } +/** + * Do not use this component anymore. + * Please, use NumberInputOption in 'required_number_input.tsx'. + * It is required for compatibility with TS 3.7.0 + * This should be removed in the future + */ function NumberInputOption({ disabled, error, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx new file mode 100644 index 0000000000000..7b62016c4e502 --- /dev/null +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/required_number_input.tsx @@ -0,0 +1,87 @@ +/* + * 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 React, { ReactNode, useCallback, ChangeEvent } from 'react'; +import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; +import { useValidation } from './utils'; + +interface NumberInputOptionProps { + disabled?: boolean; + error?: ReactNode; + isInvalid?: boolean; + label?: React.ReactNode; + max?: number; + min?: number; + paramName: ParamName; + step?: number; + value: number | null; + 'data-test-subj'?: string; + setValue(paramName: ParamName, value: number | null): void; + setValidity(paramName: ParamName, isValid: boolean): void; +} + +/** + * Use only this component instead of NumberInputOption in 'number_input.tsx'. + * It is required for compatibility with TS 3.7.0 + * + * @param {number} props.value Should be numeric only + */ +function NumberInputOption({ + disabled, + error, + isInvalid, + label, + max, + min, + paramName, + step, + value, + setValue, + setValidity, + 'data-test-subj': dataTestSubj, +}: NumberInputOptionProps) { + const isValid = value !== null; + useValidation(setValidity, paramName, isValid); + + const onChange = useCallback( + (ev: ChangeEvent) => + setValue(paramName, isNaN(ev.target.valueAsNumber) ? null : ev.target.valueAsNumber), + [setValue, paramName] + ); + + return ( + + + + ); +} + +export { NumberInputOption }; diff --git a/src/legacy/core_plugins/interpreter/public/functions/index.ts b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts similarity index 64% rename from src/legacy/core_plugins/interpreter/public/functions/index.ts rename to src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts index d86f033acb3d1..d51631106dda7 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/index.ts +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/utils.ts @@ -17,22 +17,18 @@ * under the License. */ -import { clog } from './clog'; -import { esaggs } from './esaggs'; -import { font } from './font'; -import { kibana } from './kibana'; -import { kibanaContext } from './kibana_context'; -import { range } from './range'; -import { visualization } from './visualization'; -import { visDimension } from './vis_dimension'; +import { useEffect } from 'react'; -export const functions = [ - clog, - esaggs, - font, - kibana, - kibanaContext, - range, - visualization, - visDimension, -]; +function useValidation( + setValidity: (paramName: ParamName, isValid: boolean) => void, + paramName: ParamName, + isValid: boolean +) { + useEffect(() => { + setValidity(paramName, isValid); + + return () => setValidity(paramName, true); + }, [isValid, paramName, setValidity]); +} + +export { useValidation }; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx index 8e3f66d12b9bd..ed3b52b83c234 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/point_series.tsx @@ -21,13 +21,12 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { BasicOptions, SwitchOption } from '../../common'; +import { BasicOptions, SwitchOption, ValidationVisOptionsProps } from '../../common'; import { GridPanel } from './grid_panel'; import { ThresholdPanel } from './threshold_panel'; import { BasicVislibParams } from '../../../types'; -function PointSeriesOptions(props: VisOptionsProps) { +function PointSeriesOptions(props: ValidationVisOptionsProps) { const { stateParams, setValue, vis } = props; return ( diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx index 49e56e377a8d5..591ad2eb3a001 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/options/point_series/threshold_panel.tsx @@ -21,11 +21,16 @@ import { EuiPanel, EuiTitle, EuiColorPicker, EuiFormRow, EuiSpacer } from '@elas import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'ui/vis/editors/default'; -import { NumberInputOption, SelectOption, SwitchOption } from '../../common'; +import { SelectOption, SwitchOption, ValidationVisOptionsProps } from '../../common'; +import { NumberInputOption } from '../../common/required_number_input'; import { BasicVislibParams } from '../../../types'; -function ThresholdPanel({ stateParams, setValue, vis }: VisOptionsProps) { +function ThresholdPanel({ + stateParams, + setValue, + setMultipleValidity, + vis, +}: ValidationVisOptionsProps) { const setThresholdLine = useCallback( ( paramName: T, @@ -39,6 +44,12 @@ function ThresholdPanel({ stateParams, setValue, vis }: VisOptionsProps + setMultipleValidity(`thresholdLine__${paramName}`, isValid), + [setMultipleValidity] + ); + return ( @@ -72,6 +83,7 @@ function ThresholdPanel({ stateParams, setValue, vis }: VisOptionsProps ) => ( + + ), }, ]; } diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js index 9f3561f39101d..e024e98acb343 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.js @@ -460,6 +460,31 @@ function migrateFiltersAggQueryStringQueries(doc) { } +function migrateSubTypeAndParentFieldProperties(doc) { + if (!doc.attributes.fields) return doc; + + const fieldsString = doc.attributes.fields; + const fields = JSON.parse(fieldsString); + const migratedFields = fields.map(field => { + if (field.subType === 'multi') { + return { + ...omit(field, 'parent'), + subType: { multi: { parent: field.parent } } + }; + } + + return field; + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + fields: JSON.stringify(migratedFields), + } + }; +} + const executeMigrations720 = flow( migratePercentileRankAggregation, migrateDateHistogramAggregation @@ -490,6 +515,9 @@ export const migrations = { doc.attributes.typeMeta = doc.attributes.typeMeta || undefined; return doc; }, + '7.6.0': flow( + migrateSubTypeAndParentFieldProperties + ) }, visualization: { /** diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/legacy/core_plugins/kibana/migrations/migrations.test.js index 5368169d6dd6d..14f1e8c80e349 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.test.js @@ -56,6 +56,29 @@ Object { `); }); }); + + describe('7.6.0', function () { + const migrate = doc => migrations['index-pattern']['7.6.0'](doc); + + it('should remove the parent property and update the subType prop on every field that has them', () => { + const input = { + attributes: { + title: 'test', + // eslint-disable-next-line max-len + fields: '[{"name":"customer_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":"multi","parent":"customer_name"}]', + }, + }; + const expected = { + attributes: { + title: 'test', + // eslint-disable-next-line max-len + fields: '[{"name":"customer_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_name"}}}]', + }, + }; + + expect(migrate(input)).toEqual(expected); + }); + }); }); describe('visualization', () => { diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png new file mode 100644 index 0000000000000..4b3020d91d57d Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg new file mode 100644 index 0000000000000..72f0958f52824 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg @@ -0,0 +1,666 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 6c809e84c8c84..fe741a357cbfe 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -58,6 +58,9 @@ import 'ui/agg_response'; import 'ui/agg_types'; import { showAppRedirectNotification } from 'ui/notify'; import 'leaflet'; +import { localApplicationService } from './local_application_service'; + +localApplicationService.attachToAngular(routes); routes.enable(); diff --git a/src/plugins/expressions/common/index.ts b/src/legacy/core_plugins/kibana/public/local_application_service/index.ts similarity index 94% rename from src/plugins/expressions/common/index.ts rename to src/legacy/core_plugins/kibana/public/local_application_service/index.ts index af870208f4865..2128355ca906a 100644 --- a/src/plugins/expressions/common/index.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './expressions'; +export * from './local_application_service'; diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts new file mode 100644 index 0000000000000..9d87e187fd1e1 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -0,0 +1,145 @@ +/* + * 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 { App, AppUnmount } from 'kibana/public'; +import { UIRoutes } from 'ui/routes'; +import { ILocationService, IScope } from 'angular'; +import { npStart } from 'ui/new_platform'; +import { htmlIdGenerator } from '@elastic/eui'; + +interface ForwardDefinition { + legacyAppId: string; + newAppId: string; + keepPrefix: boolean; +} + +const matchAllWithPrefix = (prefixOrApp: string | App) => + `/${typeof prefixOrApp === 'string' ? prefixOrApp : prefixOrApp.id}/:tail*?`; + +/** + * To be able to migrate and shim parts of the Kibana app plugin + * while still running some parts of it in the legacy world, this + * service emulates the core application service while using the global + * angular router to switch between apps without page reload. + * + * The id of the apps is used as prefix of the route - when switching between + * to apps, the current application is unmounted. + * + * This service becomes unnecessary once the platform provides a central + * router that handles switching between applications without page reload. + */ +export class LocalApplicationService { + private apps: App[] = []; + private forwards: ForwardDefinition[] = []; + private idGenerator = htmlIdGenerator('kibanaAppLocalApp'); + + /** + * Register an app to be managed by the application service. + * This method works exactly as `core.application.register`. + * + * When an app is mounted, it is responsible for routing. The app + * won't be mounted again if the route changes within the prefix + * of the app (its id). It is fine to use whatever means for handling + * routing within the app. + * + * When switching to a URL outside of the current prefix, the app router + * shouldn't do anything because it doesn't own the routing anymore - + * the local application service takes over routing again, + * unmounts the current app and mounts the next app. + * + * @param app The app descriptor + */ + register(app: App) { + this.apps.push(app); + } + + /** + * Forwards every URL starting with `legacyAppId` to the same URL starting + * with `newAppId` - e.g. `/legacy/my/legacy/path?q=123` gets forwarded to + * `/newApp/my/legacy/path?q=123`. + * + * When setting the `keepPrefix` option, the new app id is simply prepended. + * The example above would become `/newApp/legacy/my/legacy/path?q=123`. + * + * This method can be used to provide backwards compatibility for URLs when + * renaming or nesting plugins. For route changes after the prefix, please + * use the routing mechanism of your app. + * + * @param legacyAppId The name of the old app to forward URLs from + * @param newAppId The name of the new app that handles the URLs now + * @param options Whether the prefix of the old app is kept to nest the legacy + * path into the new path + */ + forwardApp( + legacyAppId: string, + newAppId: string, + options: { keepPrefix: boolean } = { keepPrefix: false } + ) { + this.forwards.push({ legacyAppId, newAppId, ...options }); + } + + /** + * Wires up listeners to handle mounting and unmounting of apps to + * the legacy angular route manager. Once all apps within the Kibana + * plugin are using the local route manager, this implementation can + * be switched to a more lightweight implementation. + * + * @param angularRouteManager The current `ui/routes` instance + */ + attachToAngular(angularRouteManager: UIRoutes) { + this.apps.forEach(app => { + const wrapperElementId = this.idGenerator(); + angularRouteManager.when(matchAllWithPrefix(app), { + outerAngularWrapperRoute: true, + reloadOnSearch: false, + reloadOnUrl: false, + template: `
`, + controller($scope: IScope) { + const element = document.getElementById(wrapperElementId)!; + let unmountHandler: AppUnmount | null = null; + let isUnmounted = false; + $scope.$on('$destroy', () => { + if (unmountHandler) { + unmountHandler(); + } + isUnmounted = true; + }); + (async () => { + unmountHandler = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); + // immediately unmount app if scope got destroyed in the meantime + if (isUnmounted) { + unmountHandler(); + } + })(); + }, + }); + }); + + this.forwards.forEach(({ legacyAppId, newAppId, keepPrefix }) => { + angularRouteManager.when(matchAllWithPrefix(legacyAppId), { + resolveRedirectTo: ($location: ILocationService) => { + const url = $location.url(); + return `/${newAppId}${keepPrefix ? url : url.replace(legacyAppId, '')}`; + }, + }); + }); + } +} + +export const localApplicationService = new LocalApplicationService(); diff --git a/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js b/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js index 44fd6833c68f8..4ffe790bd51a1 100644 --- a/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js +++ b/src/legacy/core_plugins/kibana/server/routes/api/suggestions/register_value_suggestions.js @@ -28,16 +28,28 @@ export function registerValueSuggestions(server) { method: ['POST'], handler: async function (req) { const { index } = req.params; - const { field, query, boolFilter } = req.payload; + const { field: fieldName, query, boolFilter } = req.payload; const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); + + const savedObjectsClient = req.getSavedObjectsClient(); + const savedObjectsResponse = await savedObjectsClient.find( + { type: 'index-pattern', fields: ['fields'], search: `"${index}"`, searchFields: ['title'] } + ); + const indexPattern = savedObjectsResponse.total > 0 ? savedObjectsResponse.saved_objects[0] : null; + const fields = indexPattern ? JSON.parse(indexPattern.attributes.fields) : null; + const field = fields ? fields.find((field) => field.name === fieldName) : fieldName; + const body = getBody( { field, query, boolFilter }, autocompleteTerminateAfter, autocompleteTimeout ); + try { const response = await callWithRequest(req, 'search', { index, body }); - const buckets = get(response, 'aggregations.suggestions.buckets') || []; + const buckets = get(response, 'aggregations.suggestions.buckets') + || get(response, 'aggregations.nestedSuggestions.suggestions.buckets') + || []; const suggestions = map(buckets, 'key'); return suggestions; } catch (error) { @@ -55,7 +67,7 @@ function getBody({ field, query, boolFilter = [] }, terminateAfter, timeout) { // the amount of information that needs to be transmitted to the coordinating node const shardSize = 10; - return { + const body = { size: 0, timeout: `${timeout}ms`, terminate_after: terminateAfter, @@ -67,7 +79,7 @@ function getBody({ field, query, boolFilter = [] }, terminateAfter, timeout) { aggs: { suggestions: { terms: { - field, + field: field.name || field, include: `${getEscapedQuery(query)}.*`, execution_hint: executionHint, shard_size: shardSize, @@ -75,6 +87,22 @@ function getBody({ field, query, boolFilter = [] }, terminateAfter, timeout) { }, }, }; + + if (field.subType && field.subType.nested) { + return { + ...body, + aggs: { + nestedSuggestions: { + nested: { + path: field.subType.nested.path, + }, + aggs: body.aggs, + }, + } + }; + } + + return body; } function getEscapedQuery(query = '') { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js b/src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js new file mode 100644 index 0000000000000..e05be3bda145f --- /dev/null +++ b/src/legacy/core_plugins/kibana/server/tutorials/cockroachdb_metrics/index.js @@ -0,0 +1,63 @@ +/* + * 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 { TUTORIAL_CATEGORY } from '../../../common/tutorials/tutorial_category'; +import { onPremInstructions, cloudInstructions, onPremCloudInstructions } from '../../../common/tutorials/metricbeat_instructions'; + +export function cockroachdbMetricsSpecProvider(server, context) { + const moduleName = 'cockroachdb'; + return { + id: 'cockroachdbMetrics', + name: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.nameTitle', { + defaultMessage: 'CockroachDB metrics', + }), + category: TUTORIAL_CATEGORY.METRICS, + shortDescription: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.shortDescription', { + defaultMessage: 'Fetch monitoring metrics from the CockroachDB server.', + }), + longDescription: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.longDescription', { + defaultMessage: 'The `cockroachdb` Metricbeat module fetches monitoring metrics from CockroachDB. \ +[Learn more]({learnMoreLink}).', + values: { + learnMoreLink: '{config.docs.beats.metricbeat}/metricbeat-module-cockroachdb.html', + }, + }), + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/cockroachdb.svg', + artifacts: { + dashboards: [ + { + id: 'e3ba0c30-9766-11e9-9eea-6f554992ec1f', + linkLabel: i18n.translate('kbn.server.tutorials.cockroachdbMetrics.artifacts.dashboards.linkLabel', { + defaultMessage: 'CockroachDB metrics dashboard', + }), + isOverview: true + } + ], + exportedFields: { + documentationUrl: '{config.docs.beats.metricbeat}/exported-fields-cockroachdb.html' + } + }, + completionTimeMinutes: 10, + previewImagePath: '/plugins/kibana/home/tutorial_resources/cockroachdb_metrics/screenshot.png', + onPrem: onPremInstructions(moduleName, null, null, null, context), + elasticCloud: cloudInstructions(moduleName), + onPremElasticCloud: onPremCloudInstructions(moduleName) + }; +} diff --git a/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js b/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js index 69f31c623e5ef..ca308ac969e49 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/coredns_logs/index.js @@ -46,7 +46,7 @@ export function corednsLogsSpecProvider(server, context) { learnMoreLink: '{config.docs.beats.filebeat}/filebeat-module-coredns.html', }, }), - //TODO: euiIconType: 'logoCoredns', + euiIconType: '/plugins/kibana/home/tutorial_resources/logos/coredns.svg', artifacts: { dashboards: [ { diff --git a/src/legacy/core_plugins/kibana/server/tutorials/register.js b/src/legacy/core_plugins/kibana/server/tutorials/register.js index 70bc0f4d3c9eb..a95baad4ccb65 100644 --- a/src/legacy/core_plugins/kibana/server/tutorials/register.js +++ b/src/legacy/core_plugins/kibana/server/tutorials/register.js @@ -78,6 +78,7 @@ import { envoyproxyLogsSpecProvider } from './envoyproxy_logs'; import { couchdbMetricsSpecProvider } from './couchdb_metrics'; import { emsBoundariesSpecProvider } from './ems'; import { consulMetricsSpecProvider } from './consul_metrics'; +import { cockroachdbMetricsSpecProvider } from './cockroachdb_metrics'; export function registerTutorials(server) { server.registerTutorial(systemLogsSpecProvider); @@ -141,4 +142,5 @@ export function registerTutorials(server) { server.registerTutorial(couchdbMetricsSpecProvider); server.registerTutorial(emsBoundariesSpecProvider); server.registerTutorial(consulMetricsSpecProvider); + server.registerTutorial(cockroachdbMetricsSpecProvider); } diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts index 89f74ef4b53e9..3b2e503a01e38 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.ts @@ -171,7 +171,7 @@ export const createMetricVisFn = (): ExpressionFunction< throw new Error('colorRange must be provided when using percentage'); } - const fontSize = Number.parseInt(args.font.spec.fontSize, 10); + const fontSize = Number.parseInt(args.font.spec.fontSize || '', 10); return { type: 'render', diff --git a/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js b/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js index 87a1f1c9ece47..da44cc618eedd 100644 --- a/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js +++ b/src/legacy/server/sample_data/data_sets/ecommerce/saved_objects.js @@ -269,7 +269,7 @@ export const getSavedObjects = () => [ "attributes": { "title": "kibana_sample_data_ecommerce", "timeFieldName": "order_date", - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"category\",\"subType\":\"multi\"},{\"name\":\"currency\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_birth_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_first_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_first_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_first_name\",\"subType\":\"multi\"},{\"name\":\"customer_full_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_full_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_full_name\",\"subType\":\"multi\"},{\"name\":\"customer_gender\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_last_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_last_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_last_name\",\"subType\":\"multi\"},{\"name\":\"customer_phone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"day_of_week\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"day_of_week_i\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"manufacturer\",\"subType\":\"multi\"},{\"name\":\"order_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"order_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products._id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products._id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"products._id\",\"subType\":\"multi\"},{\"name\":\"products.base_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.base_unit_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"products.category\",\"subType\":\"multi\"},{\"name\":\"products.created_on\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.discount_percentage\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"products.manufacturer\",\"subType\":\"multi\"},{\"name\":\"products.min_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.product_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.product_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.product_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"products.product_name\",\"subType\":\"multi\"},{\"name\":\"products.quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.tax_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.taxful_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.taxless_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.unit_discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"taxful_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"taxless_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"total_quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"total_unique_products\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"category\"}}},{\"name\":\"currency\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_birth_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_first_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_first_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_first_name\"}}},{\"name\":\"customer_full_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_full_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_full_name\"}}},{\"name\":\"customer_gender\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"customer_last_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_last_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_last_name\"}}},{\"name\":\"customer_phone\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"day_of_week\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"day_of_week_i\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"email\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.city_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.continent_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.country_iso_code\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geoip.region_name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"manufacturer\"}}},{\"name\":\"order_date\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"order_id\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products._id\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products._id.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"products._id\"}}},{\"name\":\"products.base_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.base_unit_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.category\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.category.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"products.category\"}}},{\"name\":\"products.created_on\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.discount_percentage\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.manufacturer\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.manufacturer.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"products.manufacturer\"}}},{\"name\":\"products.min_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.product_id\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.product_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"products.product_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"products.product_name\"}}},{\"name\":\"products.quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.tax_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.taxful_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.taxless_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"products.unit_discount_amount\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"sku\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"taxful_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"taxless_total_price\",\"type\":\"number\",\"esTypes\":[\"half_float\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"total_quantity\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"total_unique_products\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"type\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"user\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", "fieldFormatMap": "{\"taxful_total_price\":{\"id\":\"number\",\"params\":{\"pattern\":\"$0,0.[00]\"}}}" } }, diff --git a/src/legacy/server/sample_data/data_sets/logs/saved_objects.js b/src/legacy/server/sample_data/data_sets/logs/saved_objects.js index 234436349b02f..522f43d107b24 100644 --- a/src/legacy/server/sample_data/data_sets/logs/saved_objects.js +++ b/src/legacy/server/sample_data/data_sets/logs/saved_objects.js @@ -241,7 +241,7 @@ export const getSavedObjects = () => [ "attributes": { "title": "kibana_sample_data_logs", "timeFieldName": "timestamp", - "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"agent\",\"subType\":\"multi\"},{\"name\":\"bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"extension\",\"subType\":\"multi\"},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"host\",\"subType\":\"multi\"},{\"name\":\"index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"index\",\"subType\":\"multi\"},{\"name\":\"ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"machine.os\",\"subType\":\"multi\"},{\"name\":\"machine.ram\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"message\",\"subType\":\"multi\"},{\"name\":\"phpmemory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"request\",\"subType\":\"multi\"},{\"name\":\"response\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"response\",\"subType\":\"multi\"},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"tags.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"tags\",\"subType\":\"multi\"},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"url\",\"subType\":\"multi\"},{\"name\":\"utc_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "fields": "[{\"name\":\"@timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"event.dataset\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"host\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"name\":\"index\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"esTypes\":[\"ip\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"machine.os\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"esTypes\":[\"double\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"message\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"message.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"message\"}}},{\"name\":\"phpmemory\",\"type\":\"number\",\"esTypes\":[\"long\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"request\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"name\":\"tags\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"tags.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"tags\"}}},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", "fieldFormatMap": "{\"hour_of_day\":{}}" } }, diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 785b0345aa999..27484fb88f22e 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -18,6 +18,7 @@ */ import { + auto, ICompileProvider, IHttpProvider, IHttpService, @@ -48,6 +49,12 @@ import { isSystemApiRequest } from '../system_api'; const URL_LIMIT_WARN_WITHIN = 1000; +function isDummyWrapperRoute($route: any) { + return ( + $route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute + ); +} + export const configureAppAngularModule = (angularModule: IModule) => { const newPlatform = npStart.core; const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); @@ -187,6 +194,9 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -226,6 +236,9 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (badgeSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -270,6 +283,9 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; $rootScope.$on('$routeChangeStart', () => { + if (isDummyWrapperRoute($route)) { + return; + } helpExtensionSetSinceRouteChange = false; }); @@ -286,10 +302,15 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( $location: ILocationService, - $rootScope: IRootScopeService + $rootScope: IRootScopeService, + $injector: auto.IInjectorService ) => { + const $route = $injector.has('$route') ? $injector.get('$route') : {}; const urlOverflow = new UrlOverflowService(); const check = () => { + if (isDummyWrapperRoute($route)) { + return; + } // disable long url checks when storing state in session storage if (newPlatform.uiSettings.get('state:storeInSessionStorage')) { return; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index c74288c98d79c..611a182cf5d7f 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -36,6 +36,11 @@ export const npSetup = { register: () => undefined, get: () => null, }, + getExecutor: () => ({ + interpreter: { + interpretAst: () => {}, + }, + }), }, }, data: { diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts index 3471d7e954862..56203354f3c20 100644 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ b/src/legacy/ui/public/routes/route_manager.d.ts @@ -26,7 +26,10 @@ import { ChromeBreadcrumb } from '../../../../core/public'; interface RouteConfiguration { controller?: string | ((...args: any[]) => void); redirectTo?: string; + resolveRedirectTo?: (...args: any[]) => void; reloadOnSearch?: boolean; + reloadOnUrl?: boolean; + outerAngularWrapperRoute?: boolean; resolve?: object; template?: string; k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[]; diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts index 135260ac01b17..bb9f5832ac4e5 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.ts @@ -538,16 +538,16 @@ export class EmbeddedVisualizeHandler { private rendererProvider = (response: VisResponseData | null) => { const renderer = registries.renderers.get(get(response || {}, 'as', 'visualization')); - const args = [ - this.element, - get(response, 'value', { visType: this.vis.type.name }), - this.handlers, - ]; if (!renderer) { return null; } - return () => renderer.render(...args); + return () => + renderer.render( + this.element, + get(response, 'value', { visType: this.vis.type.name }), + this.handlers + ); }; } diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts index 17ec224f4c7de..21b13abea440e 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts @@ -24,7 +24,7 @@ import { SearchSource } from 'ui/courier'; import { AggConfig, Vis, VisParams, VisState } from 'ui/vis'; import { isDateHistogramBucketAggConfig } from 'ui/agg_types/buckets/date_histogram'; import moment from 'moment'; -import { SerializedFieldFormat } from 'src/plugins/expressions/common/expressions/types/common'; +import { SerializedFieldFormat } from 'src/plugins/expressions/public'; import { createFormat } from './utilities'; interface SchemaConfigParams { diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index c12bd222663ae..c5ebc75973d0c 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { identity } from 'lodash'; import { AggConfig, Vis } from 'ui/vis'; -import { SerializedFieldFormat } from 'src/plugins/expressions/common/expressions/types/common'; +import { SerializedFieldFormat } from 'src/plugins/expressions/public'; import { FieldFormat } from '../../../../../../plugins/data/common/field_formats'; diff --git a/src/plugins/data/public/autocomplete_provider/types.ts b/src/plugins/data/public/autocomplete_provider/types.ts index 4b1c5dfbd3528..1f2d8f914dde3 100644 --- a/src/plugins/data/public/autocomplete_provider/types.ts +++ b/src/plugins/data/public/autocomplete_provider/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { StaticIndexPattern } from 'ui/index_patterns'; +import { StaticIndexPattern, Field } from 'ui/index_patterns'; import { AutocompleteProviderRegister } from '.'; export type AutocompletePublicPluginSetup = Pick< @@ -50,11 +50,22 @@ export type AutocompleteSuggestionType = | 'conjunction' | 'recentSearch'; +// A union type allows us to do easy type guards in the code. For example, if I want to ensure I'm +// working with a FieldAutocompleteSuggestion, I can just do `if ('field' in suggestion)` and the +// TypeScript compiler will narrow the type to the parts of the union that have a field prop. /** @public **/ -export interface AutocompleteSuggestion { +export type AutocompleteSuggestion = BasicAutocompleteSuggestion | FieldAutocompleteSuggestion; + +interface BasicAutocompleteSuggestion { description?: string; end: number; start: number; text: string; type: AutocompleteSuggestionType; + cursorIndex?: number; } + +export type FieldAutocompleteSuggestion = BasicAutocompleteSuggestion & { + type: 'field'; + field: Field; +}; diff --git a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts index 96613e993a99a..5f1493a49ab7d 100644 --- a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts @@ -28,8 +28,12 @@ export interface FieldDescriptor { searchable: boolean; type: string; esTypes: string[]; - parent?: string; - subType?: string; + subType?: FieldSubType; +} + +interface FieldSubType { + multi?: { parent: string }; + nested?: { path: string }; } export class IndexPatternsFetcher { diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json index 97f8c187e1680..0d70f49b5e6b5 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/__fixtures__/es_field_caps_response.json @@ -221,6 +221,27 @@ "searchable": true, "aggregatable": true } + }, + "nested_object_parent": { + "nested": { + "type": "nested", + "searchable": false, + "aggregatable": false + } + }, + "nested_object_parent.child": { + "text": { + "type": "text", + "searchable": true, + "aggregatable": false + } + }, + "nested_object_parent.child.keyword": { + "keyword": { + "type": "keyword", + "searchable": true, + "aggregatable": true + } } } } diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index dc7fef6d6136e..cf4af615b9577 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -37,10 +37,10 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { describe('conflicts', () => { it('returns a field for each in response, no filtering', () => { const fields = readFieldCapsResponse(esResponse); - expect(fields).toHaveLength(22); + expect(fields).toHaveLength(24); }); - it('includes only name, type, esTypes, searchable, aggregatable, readFromDocValues, and maybe conflictDescriptions, parent, ' + + it('includes only name, type, esTypes, searchable, aggregatable, readFromDocValues, and maybe conflictDescriptions, ' + 'and subType of each field', () => { const responseClone = cloneDeep(esResponse); // try to trick it into including an extra field @@ -48,7 +48,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { const fields = readFieldCapsResponse(responseClone); fields.forEach(field => { - const fieldWithoutOptionalKeys = omit(field, 'conflictDescriptions', 'parent', 'subType'); + const fieldWithoutOptionalKeys = omit(field, 'conflictDescriptions', 'subType'); expect(Object.keys(fieldWithoutOptionalKeys)).toEqual([ 'name', @@ -65,8 +65,8 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { sandbox.spy(shouldReadFieldFromDocValuesNS, 'shouldReadFieldFromDocValues'); const fields = readFieldCapsResponse(esResponse); const conflictCount = fields.filter(f => f.type === 'conflict').length; - // +1 is for the object field which gets filtered out of the final return value from readFieldCapsResponse - sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 1); + // +2 is for the object and nested fields which get filtered out of the final return value from readFieldCapsResponse + sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 2); }); it('converts es types to kibana types', () => { @@ -132,20 +132,41 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { expect(mixSearchableOther.searchable).toBe(true); }); - it('returns multi fields with parent and subType keys describing the relationship', () => { + it('returns multi fields with a subType key describing the relationship', () => { const fields = readFieldCapsResponse(esResponse); const child = fields.find(f => f.name === 'multi_parent.child'); - expect(child).toHaveProperty('parent', 'multi_parent'); - expect(child).toHaveProperty('subType', 'multi'); + expect(child).toHaveProperty('subType', { multi: { parent: 'multi_parent' } }); }); - it('should not confuse object children for multi field children', () => { + it('returns nested sub-fields with a subType key describing the relationship', () => { + const fields = readFieldCapsResponse(esResponse); + const child = fields.find(f => f.name === 'nested_object_parent.child'); + expect(child).toHaveProperty('subType', { nested: { path: 'nested_object_parent' } }); + }); + + it('handles fields that are both nested and multi', () => { + const fields = readFieldCapsResponse(esResponse); + const child = fields.find(f => f.name === 'nested_object_parent.child.keyword'); + expect(child).toHaveProperty( + 'subType', + { + nested: { path: 'nested_object_parent' }, + multi: { parent: 'nested_object_parent.child' } + }); + }); + + it('does not include the field actually mapped as nested itself', () => { + const fields = readFieldCapsResponse(esResponse); + const child = fields.find(f => f.name === 'nested_object_parent'); + expect(child).toBeUndefined(); + }); + + it('should not confuse object children for multi or nested field children', () => { // We detect multi fields by finding fields that have a dot in their name and then looking - // to see if their parents are *not* object or nested fields. In the future we may want to - // add parent and subType info for object and nested fields but for now we don't need it. + // to see if their parents are *not* object fields. In the future we may want to + // add subType info for object fields but for now we don't need it. const fields = readFieldCapsResponse(esResponse); const child = fields.find(f => f.name === 'object_parent.child'); - expect(child).not.toHaveProperty('parent'); expect(child).not.toHaveProperty('subType'); }); }); diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index cd3303021ee08..06eb30db0b24b 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -151,18 +151,38 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie return field.name.includes('.'); }); - // Discern which sub fields are multi fields. If the parent field is not an object or nested field - // the child must be a multi field. + // Determine the type of each sub field. subFields.forEach(field => { - const parentFieldName = field.name + const parentFieldNames = field.name .split('.') .slice(0, -1) - .join('.'); - const parentFieldCaps = kibanaFormattedCaps.find(caps => caps.name === parentFieldName); + .map((_, index, parentFieldNameParts) => { + return parentFieldNameParts.slice(0, index + 1).join('.'); + }); + const parentFieldCaps = parentFieldNames.map(parentFieldName => { + return kibanaFormattedCaps.find(caps => caps.name === parentFieldName); + }); + const parentFieldCapsAscending = parentFieldCaps.reverse(); + + if (parentFieldCaps && parentFieldCaps.length > 0) { + let subType = {}; + // If the parent field is not an object or nested field the child must be a multi field. + const firstParent = parentFieldCapsAscending[0]; + if (firstParent && !['object', 'nested'].includes(firstParent.type)) { + subType = { ...subType, multi: { parent: firstParent.name } }; + } + + // We need to know if any parent field is nested + const nestedParentCaps = parentFieldCapsAscending.find( + parentCaps => parentCaps && parentCaps.type === 'nested' + ); + if (nestedParentCaps) { + subType = { ...subType, nested: { path: nestedParentCaps.name } }; + } - if (parentFieldCaps && !['object', 'nested'].includes(parentFieldCaps.type)) { - field.parent = parentFieldName; - field.subType = 'multi'; + if (Object.keys(subType).length > 0) { + field.subType = subType; + } } }); diff --git a/src/plugins/expressions/kibana.json b/src/plugins/expressions/kibana.json index ce2b1854cf415..ec87b56f3745e 100644 --- a/src/plugins/expressions/kibana.json +++ b/src/plugins/expressions/kibana.json @@ -2,5 +2,8 @@ "id": "expressions", "version": "kibana", "server": false, - "ui": true + "ui": true, + "requiredPlugins": [ + "inspector" + ] } diff --git a/src/plugins/expressions/common/expressions/create_error.ts b/src/plugins/expressions/public/create_error.ts similarity index 100% rename from src/plugins/expressions/common/expressions/create_error.ts rename to src/plugins/expressions/public/create_error.ts diff --git a/src/plugins/expressions/public/create_handlers.ts b/src/plugins/expressions/public/create_handlers.ts new file mode 100644 index 0000000000000..46e85411c5895 --- /dev/null +++ b/src/plugins/expressions/public/create_handlers.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +export function createHandlers() { + return { + environment: 'client', + }; +} diff --git a/src/plugins/expressions/common/expressions/expression_types/boolean.ts b/src/plugins/expressions/public/expression_types/boolean.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/boolean.ts rename to src/plugins/expressions/public/expression_types/boolean.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/datatable.ts b/src/plugins/expressions/public/expression_types/datatable.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/datatable.ts rename to src/plugins/expressions/public/expression_types/datatable.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/error.ts b/src/plugins/expressions/public/expression_types/error.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/error.ts rename to src/plugins/expressions/public/expression_types/error.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/filter.ts b/src/plugins/expressions/public/expression_types/filter.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/filter.ts rename to src/plugins/expressions/public/expression_types/filter.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/image.ts b/src/plugins/expressions/public/expression_types/image.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/image.ts rename to src/plugins/expressions/public/expression_types/image.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/index.ts b/src/plugins/expressions/public/expression_types/index.ts similarity index 93% rename from src/plugins/expressions/common/expressions/expression_types/index.ts rename to src/plugins/expressions/public/expression_types/index.ts index bb8a554487f01..a5d182fee75ed 100644 --- a/src/plugins/expressions/common/expressions/expression_types/index.ts +++ b/src/plugins/expressions/public/expression_types/index.ts @@ -22,6 +22,8 @@ import { datatable } from './datatable'; import { error } from './error'; import { filter } from './filter'; import { image } from './image'; +import { kibanaContext } from './kibana_context'; +import { kibanaDatatable } from './kibana_datatable'; import { nullType } from './null'; import { number } from './number'; import { pointseries } from './pointseries'; @@ -30,8 +32,6 @@ import { render } from './render'; import { shape } from './shape'; import { string } from './string'; import { style } from './style'; -import { kibanaContext } from './kibana_context'; -import { kibanaDatatable } from './kibana_datatable'; export const typeSpecs = [ boolean, @@ -39,26 +39,30 @@ export const typeSpecs = [ error, filter, image, - number, + kibanaContext, + kibanaDatatable, nullType, + number, pointseries, range, render, shape, string, style, - kibanaContext, - kibanaDatatable, ]; -// Types +export * from './boolean'; export * from './datatable'; export * from './error'; export * from './filter'; export * from './image'; export * from './kibana_context'; export * from './kibana_datatable'; +export * from './null'; +export * from './number'; export * from './pointseries'; +export * from './range'; export * from './render'; +export * from './shape'; +export * from './string'; export * from './style'; -export * from './range'; diff --git a/src/plugins/expressions/common/expressions/expression_types/kibana_context.ts b/src/plugins/expressions/public/expression_types/kibana_context.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/kibana_context.ts rename to src/plugins/expressions/public/expression_types/kibana_context.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/kibana_datatable.ts b/src/plugins/expressions/public/expression_types/kibana_datatable.ts similarity index 97% rename from src/plugins/expressions/common/expressions/expression_types/kibana_datatable.ts rename to src/plugins/expressions/public/expression_types/kibana_datatable.ts index 7f77e226ff1d9..c360a2be8c7f7 100644 --- a/src/plugins/expressions/common/expressions/expression_types/kibana_datatable.ts +++ b/src/plugins/expressions/public/expression_types/kibana_datatable.ts @@ -19,7 +19,7 @@ import { map } from 'lodash'; import { SerializedFieldFormat } from '../types/common'; -import { Datatable, PointSeries } from '../types'; +import { Datatable, PointSeries } from '.'; const name = 'kibana_datatable'; diff --git a/src/plugins/expressions/common/expressions/expression_types/null.ts b/src/plugins/expressions/public/expression_types/null.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/null.ts rename to src/plugins/expressions/public/expression_types/null.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/number.ts b/src/plugins/expressions/public/expression_types/number.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/number.ts rename to src/plugins/expressions/public/expression_types/number.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/pointseries.ts b/src/plugins/expressions/public/expression_types/pointseries.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/pointseries.ts rename to src/plugins/expressions/public/expression_types/pointseries.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/range.ts b/src/plugins/expressions/public/expression_types/range.ts similarity index 94% rename from src/plugins/expressions/common/expressions/expression_types/range.ts rename to src/plugins/expressions/public/expression_types/range.ts index c1a0e4d2075fa..082056c909988 100644 --- a/src/plugins/expressions/common/expressions/expression_types/range.ts +++ b/src/plugins/expressions/public/expression_types/range.ts @@ -17,7 +17,8 @@ * under the License. */ -import { ExpressionType, Render } from '../../../common/expressions/types'; +import { ExpressionType } from '../types'; +import { Render } from '.'; const name = 'range'; diff --git a/src/plugins/expressions/common/expressions/expression_types/render.ts b/src/plugins/expressions/public/expression_types/render.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/render.ts rename to src/plugins/expressions/public/expression_types/render.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/shape.ts b/src/plugins/expressions/public/expression_types/shape.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/shape.ts rename to src/plugins/expressions/public/expression_types/shape.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/string.ts b/src/plugins/expressions/public/expression_types/string.ts similarity index 100% rename from src/plugins/expressions/common/expressions/expression_types/string.ts rename to src/plugins/expressions/public/expression_types/string.ts diff --git a/src/plugins/expressions/common/expressions/expression_types/style.ts b/src/plugins/expressions/public/expression_types/style.ts similarity index 83% rename from src/plugins/expressions/common/expressions/expression_types/style.ts rename to src/plugins/expressions/public/expression_types/style.ts index f6ef0f1fe42e6..d93893d25c11c 100644 --- a/src/plugins/expressions/common/expressions/expression_types/style.ts +++ b/src/plugins/expressions/public/expression_types/style.ts @@ -17,17 +17,11 @@ * under the License. */ -import { ExpressionType } from '../types'; +import { ExpressionType, ExpressionTypeStyle } from '../types'; const name = 'style'; -export interface Style { - type: typeof name; - spec: any; - css: string; -} - -export const style = (): ExpressionType => ({ +export const style = (): ExpressionType => ({ name, from: { null: () => { diff --git a/src/plugins/expressions/public/expressions/expressions_service.ts b/src/plugins/expressions/public/expressions/expressions_service.ts deleted file mode 100644 index 4b6820143b794..0000000000000 --- a/src/plugins/expressions/public/expressions/expressions_service.ts +++ /dev/null @@ -1,69 +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 { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './interpreter'; -import { AnyExpressionType, AnyExpressionFunction } from '../../common/expressions/types'; - -export interface ExpressionsSetupContract { - registerFunction: (fn: () => AnyExpressionFunction) => void; - registerRenderer: (renderer: any) => void; - registerType: (type: () => AnyExpressionType) => void; - __LEGACY: { - functions: FunctionsRegistry; - renderers: RenderFunctionsRegistry; - types: TypesRegistry; - }; -} - -export type ExpressionsStartContract = ExpressionsSetupContract; - -export class ExpressionsService { - private readonly functions = new FunctionsRegistry(); - private readonly renderers = new RenderFunctionsRegistry(); - private readonly types = new TypesRegistry(); - - private setupApi!: ExpressionsSetupContract; - - public setup() { - const { functions, renderers, types } = this; - - this.setupApi = { - registerFunction: fn => { - this.functions.register(fn); - }, - registerRenderer: (renderer: any) => { - this.renderers.register(renderer); - }, - registerType: type => { - this.types.register(type); - }, - __LEGACY: { - functions, - renderers, - types, - }, - }; - - return this.setupApi; - } - - public start(): ExpressionsStartContract { - return this.setupApi as ExpressionsStartContract; - } -} diff --git a/src/plugins/expressions/public/expressions/interpreter.ts b/src/plugins/expressions/public/expressions/interpreter.ts deleted file mode 100644 index f27ef57c7980a..0000000000000 --- a/src/plugins/expressions/public/expressions/interpreter.ts +++ /dev/null @@ -1,173 +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. - */ - -/** - * @todo - * This whole file needs major refactoring. `Registry` class does not do anything - * useful. "Wrappers" like `RenderFunction` basically just set default props on the objects. - */ - -/* eslint-disable max-classes-per-file */ -import { clone, mapValues, includes } from 'lodash'; -import { Type } from '../../common/expressions/interpreter'; -import { ExpressionType, AnyExpressionFunction } from '../../common/expressions/types'; - -export class Registry { - _prop: string; - _indexed: any; - - constructor(prop: string = 'name') { - if (typeof prop !== 'string') throw new Error('Registry property name must be a string'); - this._prop = prop; - this._indexed = new Object(); - } - - wrapper(obj: ItemSpec) { - return obj; - } - - register(fn: () => ItemSpec) { - if (typeof fn !== 'function') throw new Error(`Register requires an function`); - - const obj = fn() as any; - - if (typeof obj !== 'object' || !obj[this._prop]) { - throw new Error(`Registered functions must return an object with a ${this._prop} property`); - } - - this._indexed[obj[this._prop].toLowerCase()] = this.wrapper(obj); - } - - toJS(): Record { - return Object.keys(this._indexed).reduce( - (acc, key) => { - acc[key] = this.get(key); - return acc; - }, - {} as any - ); - } - - toArray(): Item[] { - return Object.keys(this._indexed).map(key => this.get(key)!); - } - - get(name: string): Item | null { - if (name === undefined) { - return null; - } - const lowerCaseName = name.toLowerCase(); - return this._indexed[lowerCaseName] ? clone(this._indexed[lowerCaseName]) : null; - } - - getProp(): string { - return this._prop; - } - - reset() { - this._indexed = new Object(); - } -} - -function RenderFunction(this: any, config: any) { - // This must match the name of the function that is used to create the `type: render` object - this.name = config.name; - - // Use this to set a more friendly name - this.displayName = config.displayName || this.name; - - // A sentence or few about what this element does - this.help = config.help; - - // used to validate the data before calling the render function - this.validate = config.validate || function validate() {}; - - // tell the renderer if the dom node should be reused, it's recreated each time by default - this.reuseDomNode = Boolean(config.reuseDomNode); - - // the function called to render the data - this.render = - config.render || - function render(domNode: any, data: any, done: any) { - done(); - }; -} - -export function Arg(this: any, config: any) { - if (config.name === '_') throw Error('Arg names must not be _. Use it in aliases instead.'); - this.name = config.name; - this.required = config.required || false; - this.help = config.help || ''; - this.types = config.types || []; - this.default = config.default; - this.aliases = config.aliases || []; - this.multi = config.multi == null ? false : config.multi; - this.resolve = config.resolve == null ? true : config.resolve; - this.options = config.options || []; - this.accepts = (type: any) => { - if (!this.types.length) return true; - return includes(config.types, type); - }; -} - -export function Fn(this: any, config: any) { - // Required - this.name = config.name; // Name of function - - // Return type of function. - // This SHOULD be supplied. We use it for UI and autocomplete hinting, - // We may also use it for optimizations in the future. - this.type = config.type; - this.aliases = config.aliases || []; - - // Function to run function (context, args) - this.fn = (...args: any) => Promise.resolve(config.fn(...args)); - - // Optional - this.help = config.help || ''; // A short help text - this.args = mapValues( - config.args || {}, - (arg: any, name: any) => new (Arg as any)({ name, ...arg }) - ); - - this.context = config.context || {}; - - this.accepts = (type: any) => { - if (!this.context.types) return true; // If you don't tell us about context, we'll assume you don't care what you get - return includes(this.context.types, type); // Otherwise, check it - }; -} - -export class RenderFunctionsRegistry extends Registry { - wrapper(obj: any) { - return new (RenderFunction as any)(obj); - } -} - -export class FunctionsRegistry extends Registry { - wrapper(obj: any) { - return new (Fn as any)(obj); - } -} - -export class TypesRegistry extends Registry, any> { - wrapper(obj: any) { - return new (Type as any)(obj); - } -} diff --git a/src/plugins/expressions/public/fonts.ts b/src/plugins/expressions/public/fonts.ts new file mode 100644 index 0000000000000..cdf3d4c16f3b5 --- /dev/null +++ b/src/plugins/expressions/public/fonts.ts @@ -0,0 +1,151 @@ +/* + * 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. + */ + +/** + * This type contains a unions of all supported font labels, or the the name of + * the font the user would see in a UI. + */ +export type FontLabel = typeof fonts[number]['label']; + +/** + * This type contains a union of all supported font values, equivalent to the CSS + * `font-value` property. + */ +export type FontValue = typeof fonts[number]['value']; + +/** + * An interface representing a font in Canvas, with a textual label and the CSS + * `font-value`. + */ +export interface Font { + label: FontLabel; + value: FontValue; +} + +// This function allows one to create a strongly-typed font for inclusion in +// the font collection. As a result, the values and labels are known to the +// type system, preventing one from specifying a non-existent font at build +// time. +function createFont< + RawFont extends { value: RawFontValue; label: RawFontLabel }, + RawFontValue extends string, + RawFontLabel extends string +>(font: RawFont) { + return font; +} + +export const americanTypewriter = createFont({ + label: 'American Typewriter', + value: "'American Typewriter', 'Courier New', Courier, Monaco, mono", +}); + +export const arial = createFont({ label: 'Arial', value: 'Arial, sans-serif' }); + +export const baskerville = createFont({ + label: 'Baskerville', + value: "Baskerville, Georgia, Garamond, 'Times New Roman', Times, serif", +}); + +export const bookAntiqua = createFont({ + label: 'Book Antiqua', + value: "'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", +}); + +export const brushScript = createFont({ + label: 'Brush Script', + value: "'Brush Script MT', 'Comic Sans', sans-serif", +}); + +export const chalkboard = createFont({ + label: 'Chalkboard', + value: "Chalkboard, 'Comic Sans', sans-serif", +}); + +export const didot = createFont({ + label: 'Didot', + value: "Didot, Georgia, Garamond, 'Times New Roman', Times, serif", +}); + +export const futura = createFont({ + label: 'Futura', + value: 'Futura, Impact, Helvetica, Arial, sans-serif', +}); + +export const gillSans = createFont({ + label: 'Gill Sans', + value: + "'Gill Sans', 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", +}); + +export const helveticaNeue = createFont({ + label: 'Helvetica Neue', + value: "'Helvetica Neue', Helvetica, Arial, sans-serif", +}); + +export const hoeflerText = createFont({ + label: 'Hoefler Text', + value: "'Hoefler Text', Garamond, Georgia, 'Times New Roman', Times, serif", +}); + +export const lucidaGrande = createFont({ + label: 'Lucida Grande', + value: "'Lucida Grande', 'Lucida Sans Unicode', Lucida, Verdana, Helvetica, Arial, sans-serif", +}); + +export const myriad = createFont({ + label: 'Myriad', + value: 'Myriad, Helvetica, Arial, sans-serif', +}); + +export const openSans = createFont({ + label: 'Open Sans', + value: "'Open Sans', Helvetica, Arial, sans-serif", +}); + +export const optima = createFont({ + label: 'Optima', + value: "Optima, 'Lucida Grande', 'Lucida Sans Unicode', Verdana, Helvetica, Arial, sans-serif", +}); + +export const palatino = createFont({ + label: 'Palatino', + value: "Palatino, 'Book Antiqua', Georgia, Garamond, 'Times New Roman', Times, serif", +}); + +/** + * A collection of supported fonts. + */ +export const fonts = [ + americanTypewriter, + arial, + baskerville, + bookAntiqua, + brushScript, + chalkboard, + didot, + futura, + gillSans, + helveticaNeue, + hoeflerText, + lucidaGrande, + myriad, + openSans, + optima, + palatino, +]; diff --git a/src/plugins/expressions/public/functions/clog.ts b/src/plugins/expressions/public/functions/clog.ts new file mode 100644 index 0000000000000..929075b882b96 --- /dev/null +++ b/src/plugins/expressions/public/functions/clog.ts @@ -0,0 +1,35 @@ +/* + * 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 { ExpressionFunction } from '../types'; + +const name = 'clog'; + +type Context = any; +type ClogExpressionFunction = ExpressionFunction; + +export const clog = (): ClogExpressionFunction => ({ + name, + args: {}, + help: 'Outputs the context to the console', + fn: context => { + console.log(context); // eslint-disable-line no-console + return context; + }, +}); diff --git a/src/plugins/expressions/public/functions/font.ts b/src/plugins/expressions/public/functions/font.ts new file mode 100644 index 0000000000000..33e28924f3ee1 --- /dev/null +++ b/src/plugins/expressions/public/functions/font.ts @@ -0,0 +1,186 @@ +/* + * 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 { openSans, FontLabel as FontFamily } from '../fonts'; +import { ExpressionFunction } from '../types'; +import { CSSStyle, FontStyle, FontWeight, Style, TextAlignment, TextDecoration } from '../types'; + +const dashify = (str: string) => { + return str + .trim() + .replace(/([a-z])([A-Z])/g, '$1-$2') + .replace(/\W/g, m => (/[À-ž]/.test(m) ? m : '-')) + .replace(/^-+|-+$/g, '') + .toLowerCase(); +}; + +const inlineStyle = (obj: Record) => { + if (!obj) return ''; + const styles = Object.keys(obj).map(key => { + const prop = dashify(key); + const line = prop.concat(':').concat(String(obj[key])); + return line; + }); + return styles.join(';'); +}; + +interface Arguments { + align?: TextAlignment; + color?: string; + family?: FontFamily; + italic?: boolean; + lHeight?: number | null; + size?: number; + underline?: boolean; + weight?: FontWeight; +} + +export function font(): ExpressionFunction<'font', null, Arguments, Style> { + return { + name: 'font', + aliases: [], + type: 'style', + help: i18n.translate('expressions_np.functions.fontHelpText', { + defaultMessage: 'Create a font style.', + }), + context: { + types: ['null'], + }, + args: { + align: { + default: 'left', + help: i18n.translate('expressions_np.functions.font.args.alignHelpText', { + defaultMessage: 'The horizontal text alignment.', + }), + options: Object.values(TextAlignment), + types: ['string'], + }, + color: { + help: i18n.translate('expressions_np.functions.font.args.colorHelpText', { + defaultMessage: 'The text color.', + }), + types: ['string'], + }, + family: { + default: `"${openSans.value}"`, + help: i18n.translate('expressions_np.functions.font.args.familyHelpText', { + defaultMessage: 'An acceptable {css} web font string', + values: { + css: 'CSS', + }, + }), + types: ['string'], + }, + italic: { + default: false, + help: i18n.translate('expressions_np.functions.font.args.italicHelpText', { + defaultMessage: 'Italicize the text?', + }), + options: [true, false], + types: ['boolean'], + }, + lHeight: { + default: null, + aliases: ['lineHeight'], + help: i18n.translate('expressions_np.functions.font.args.lHeightHelpText', { + defaultMessage: 'The line height in pixels', + }), + types: ['number', 'null'], + }, + size: { + default: 14, + help: i18n.translate('expressions_np.functions.font.args.sizeHelpText', { + defaultMessage: 'The font size in pixels', + }), + types: ['number'], + }, + underline: { + default: false, + help: i18n.translate('expressions_np.functions.font.args.underlineHelpText', { + defaultMessage: 'Underline the text?', + }), + options: [true, false], + types: ['boolean'], + }, + weight: { + default: 'normal', + help: i18n.translate('expressions_np.functions.font.args.weightHelpText', { + defaultMessage: 'The font weight. For example, {list}, or {end}.', + values: { + list: Object.values(FontWeight) + .slice(0, -1) + .map(weight => `\`"${weight}"\``) + .join(', '), + end: `\`"${Object.values(FontWeight).slice(-1)[0]}"\``, + }, + }), + options: Object.values(FontWeight), + types: ['string'], + }, + }, + fn: (_context, args) => { + if (!Object.values(FontWeight).includes(args.weight!)) { + throw new Error( + i18n.translate('expressions_np.functions.font.invalidFontWeightErrorMessage', { + defaultMessage: "Invalid font weight: '{weight}'", + values: { + weight: args.weight, + }, + }) + ); + } + if (!Object.values(TextAlignment).includes(args.align!)) { + throw new Error( + i18n.translate('expressions_np.functions.font.invalidTextAlignmentErrorMessage', { + defaultMessage: "Invalid text alignment: '{align}'", + values: { + align: args.align, + }, + }) + ); + } + + // the line height shouldn't ever be lower than the size, and apply as a + // pixel setting + const lineHeight = args.lHeight != null ? `${args.lHeight}px` : '1'; + + const spec: CSSStyle = { + fontFamily: args.family, + fontWeight: args.weight, + fontStyle: args.italic ? FontStyle.ITALIC : FontStyle.NORMAL, + textDecoration: args.underline ? TextDecoration.UNDERLINE : TextDecoration.NONE, + textAlign: args.align, + fontSize: `${args.size}px`, // apply font size as a pixel setting + lineHeight, // apply line height as a pixel setting + }; + + // conditionally apply styles based on input + if (args.color) { + spec.color = args.color; + } + + return { + type: 'style', + spec, + css: inlineStyle(spec as Record), + }; + }, + }; +} diff --git a/src/plugins/expressions/public/functions/kibana.ts b/src/plugins/expressions/public/functions/kibana.ts new file mode 100644 index 0000000000000..7ed9619675aea --- /dev/null +++ b/src/plugins/expressions/public/functions/kibana.ts @@ -0,0 +1,64 @@ +/* + * 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 { ExpressionFunction } from '../types'; +import { KibanaContext } from '../expression_types'; + +export type ExpressionFunctionKibana = ExpressionFunction< + 'kibana', + KibanaContext | null, + object, + KibanaContext +>; + +export const kibana = (): ExpressionFunctionKibana => ({ + name: 'kibana', + type: 'kibana_context', + + context: { + types: ['kibana_context', 'null'], + }, + + help: i18n.translate('expressions_np.functions.kibana.help', { + defaultMessage: 'Gets kibana global context', + }), + args: {}, + fn(context, args, handlers) { + const initialContext = handlers.getInitialContext ? handlers.getInitialContext() : {}; + + if (context && context.query) { + initialContext.query = initialContext.query.concat(context.query); + } + + if (context && context.filters) { + initialContext.filters = initialContext.filters.concat(context.filters); + } + + const timeRange = initialContext.timeRange || (context ? context.timeRange : undefined); + + return { + ...context, + type: 'kibana_context', + query: initialContext.query, + filters: initialContext.filters, + timeRange, + }; + }, +}); diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana_context.ts b/src/plugins/expressions/public/functions/kibana_context.ts similarity index 74% rename from src/legacy/core_plugins/interpreter/public/functions/kibana_context.ts rename to src/plugins/expressions/public/functions/kibana_context.ts index 1ba7b450c5409..2a0cd09734b6d 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/kibana_context.ts +++ b/src/plugins/expressions/public/functions/kibana_context.ts @@ -17,9 +17,10 @@ * under the License. */ -import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; -import { ExpressionFunction, KibanaContext } from '../../types'; +import { ExpressionFunction } from '../types'; +import { KibanaContext } from '../expression_types'; +import { savedObjects } from '../services'; interface Arguments { q?: string | null; @@ -41,7 +42,7 @@ export const kibanaContext = (): ExpressionFunctionKibanaContext => ({ context: { types: ['kibana_context', 'null'], }, - help: i18n.translate('interpreter.functions.kibana_context.help', { + help: i18n.translate('expressions_np.functions.kibana_context.help', { defaultMessage: 'Updates kibana global context', }), args: { @@ -49,45 +50,43 @@ export const kibanaContext = (): ExpressionFunctionKibanaContext => ({ types: ['string', 'null'], aliases: ['query', '_'], default: null, - help: i18n.translate('interpreter.functions.kibana_context.q.help', { + help: i18n.translate('expressions_np.functions.kibana_context.q.help', { defaultMessage: 'Specify Kibana free form text query', }), }, filters: { types: ['string', 'null'], default: '"[]"', - help: i18n.translate('interpreter.functions.kibana_context.filters.help', { + help: i18n.translate('expressions_np.functions.kibana_context.filters.help', { defaultMessage: 'Specify Kibana generic filters', }), }, timeRange: { types: ['string', 'null'], default: null, - help: i18n.translate('interpreter.functions.kibana_context.timeRange.help', { + help: i18n.translate('expressions_np.functions.kibana_context.timeRange.help', { defaultMessage: 'Specify Kibana time range filter', }), }, savedSearchId: { types: ['string', 'null'], default: null, - help: i18n.translate('interpreter.functions.kibana_context.savedSearchId.help', { + help: i18n.translate('expressions_np.functions.kibana_context.savedSearchId.help', { defaultMessage: 'Specify saved search ID to be used for queries and filters', }), }, }, async fn(context, args, handlers) { - const $injector = await chrome.dangerouslyGetActiveInjector(); - const savedSearches = $injector.get('savedSearches') as any; const queryArg = args.q ? JSON.parse(args.q) : []; let queries = Array.isArray(queryArg) ? queryArg : [queryArg]; let filters = args.filters ? JSON.parse(args.filters) : []; if (args.savedSearchId) { - const savedSearch = await savedSearches.get(args.savedSearchId); - const searchQuery = savedSearch.searchSource.getField('query'); - const searchFilters = savedSearch.searchSource.getField('filter'); - queries = queries.concat(searchQuery); - filters = filters.concat(searchFilters); + const obj = await savedObjects.get('search', args.savedSearchId); + const search = obj.attributes.kibanaSavedObjectMeta as { searchSourceJSON: string }; + const data = JSON.parse(search.searchSourceJSON) as { query: string; filter: any[] }; + queries = queries.concat(data.query); + filters = filters.concat(data.filter); } if (context && context.query) { diff --git a/src/legacy/core_plugins/interpreter/public/functions/__snapshots__/kibana.test.ts.snap b/src/plugins/expressions/public/functions/tests/__snapshots__/kibana.test.ts.snap similarity index 100% rename from src/legacy/core_plugins/interpreter/public/functions/__snapshots__/kibana.test.ts.snap rename to src/plugins/expressions/public/functions/tests/__snapshots__/kibana.test.ts.snap diff --git a/src/legacy/core_plugins/interpreter/public/functions/font.test.ts b/src/plugins/expressions/public/functions/tests/font.test.ts similarity index 97% rename from src/legacy/core_plugins/interpreter/public/functions/font.test.ts rename to src/plugins/expressions/public/functions/tests/font.test.ts index 7032e042a59ea..a0397b920f905 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/font.test.ts +++ b/src/plugins/expressions/public/functions/tests/font.test.ts @@ -17,9 +17,9 @@ * under the License. */ -import { openSans } from '../../common/lib/fonts'; -import { font } from './font'; -import { functionWrapper } from '../../test_helpers'; +import { openSans } from '../../fonts'; +import { font } from '../font'; +import { functionWrapper } from './utils'; describe('font', () => { const fn = functionWrapper(font); diff --git a/src/legacy/core_plugins/interpreter/public/functions/kibana.test.ts b/src/plugins/expressions/public/functions/tests/kibana.test.ts similarity index 93% rename from src/legacy/core_plugins/interpreter/public/functions/kibana.test.ts rename to src/plugins/expressions/public/functions/tests/kibana.test.ts index cd67825b534cd..d9489d724cc70 100644 --- a/src/legacy/core_plugins/interpreter/public/functions/kibana.test.ts +++ b/src/plugins/expressions/public/functions/tests/kibana.test.ts @@ -17,9 +17,10 @@ * under the License. */ -import { functionWrapper } from '../../test_helpers'; -import { kibana } from './kibana'; -import { KibanaContext, FunctionHandlers } from '../../types'; +import { functionWrapper } from './utils'; +import { kibana } from '../kibana'; +import { FunctionHandlers } from '../../types'; +import { KibanaContext } from '../../expression_types'; describe('interpreter/functions#kibana', () => { const fn = functionWrapper(kibana); diff --git a/src/plugins/expressions/public/functions/tests/utils.ts b/src/plugins/expressions/public/functions/tests/utils.ts new file mode 100644 index 0000000000000..5eb9c1a0d6390 --- /dev/null +++ b/src/plugins/expressions/public/functions/tests/utils.ts @@ -0,0 +1,33 @@ +/* + * 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 { mapValues } from 'lodash'; +import { AnyExpressionFunction, FunctionHandlers } from '../../types'; + +// Takes a function spec and passes in default args, +// overriding with any provided args. +export const functionWrapper = (fnSpec: () => T) => { + const spec = fnSpec(); + const defaultArgs = mapValues(spec.args, argSpec => argSpec.default); + return ( + context: object | null, + args: Record = {}, + handlers: FunctionHandlers = {} + ) => spec.fn(context, { ...defaultArgs, ...args }, handlers); +}; diff --git a/src/plugins/expressions/public/index.ts b/src/plugins/expressions/public/index.ts index 21f89db140e5a..84ad2550fda0a 100644 --- a/src/plugins/expressions/public/index.ts +++ b/src/plugins/expressions/public/index.ts @@ -26,4 +26,9 @@ export function plugin(initializerContext: PluginInitializerContext) { export { ExpressionsPublicPlugin as Plugin }; -export * from '../common'; +export * from './plugin'; +export * from './types'; +export { Type, getType } from './interpreter'; +export { interpreterProvider, ExpressionInterpret } from './interpreter_provider'; +export * from './serialize_provider'; +export * from './expression_types'; diff --git a/src/plugins/expressions/common/expressions/serialize_provider.test.ts b/src/plugins/expressions/public/interpreter.test.ts similarity index 97% rename from src/plugins/expressions/common/expressions/serialize_provider.test.ts rename to src/plugins/expressions/public/interpreter.test.ts index 774fb34938dd2..42443a2c32b82 100644 --- a/src/plugins/expressions/common/expressions/serialize_provider.test.ts +++ b/src/plugins/expressions/public/interpreter.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { getType } from './serialize_provider'; +import { getType } from './interpreter'; describe('getType()', () => { test('returns "null" string for null or undefined', () => { diff --git a/src/plugins/expressions/common/expressions/interpreter.ts b/src/plugins/expressions/public/interpreter.ts similarity index 50% rename from src/plugins/expressions/common/expressions/interpreter.ts rename to src/plugins/expressions/public/interpreter.ts index 07146b6eb3181..13bf245f233cb 100644 --- a/src/plugins/expressions/common/expressions/interpreter.ts +++ b/src/plugins/expressions/public/interpreter.ts @@ -18,8 +18,10 @@ */ import { get } from 'lodash'; +import { AnyExpressionType } from './types'; +import { ExpressionValue } from './types/types'; -function getType(node: any) { +export function getType(node: any) { if (node == null) return 'null'; if (typeof node === 'object') { if (!node.type) throw new Error('Objects must have a type property'); @@ -28,30 +30,48 @@ function getType(node: any) { return typeof node; } -export function Type(this: any, config: any) { - // Required - this.name = config.name; +export class Type { + name: string; - // Optional - this.help = config.help || ''; // A short help text + /** + * A short help text. + */ + help: string; - // Optional type validation, useful for checking function output - this.validate = config.validate || function validate() {}; + /** + * Type validation, useful for checking function output. + */ + validate: (type: any) => void | Error; - // Optional - this.create = config.create; + create: unknown; - // Optional serialization (used when passing context around client/server) - this.serialize = config.serialize; - this.deserialize = config.deserialize; + /** + * Optional serialization (used when passing context around client/server). + */ + serialize?: (value: ExpressionValue) => any; + deserialize?: (serialized: any) => ExpressionValue; - const getToFn = (type: any) => get(config, ['to', type]) || get(config, ['to', '*']); - const getFromFn = (type: any) => get(config, ['from', type]) || get(config, ['from', '*']); + constructor(private readonly config: AnyExpressionType) { + const { name, help, deserialize, serialize, validate } = config; - this.castsTo = (type: any) => typeof getToFn(type) === 'function'; - this.castsFrom = (type: any) => typeof getFromFn(type) === 'function'; + this.name = name; + this.help = help || ''; + this.validate = validate || (() => {}); - this.to = (node: any, toTypeName: any, types: any) => { + // Optional + this.create = (config as any).create; + + this.serialize = serialize; + this.deserialize = deserialize; + } + + getToFn = (value: any) => get(this.config, ['to', value]) || get(this.config, ['to', '*']); + getFromFn = (value: any) => get(this.config, ['from', value]) || get(this.config, ['from', '*']); + + castsTo = (value: any) => typeof this.getToFn(value) === 'function'; + castsFrom = (value: any) => typeof this.getFromFn(value) === 'function'; + + to = (node: any, toTypeName: any, types: any) => { const typeName = getType(node); if (typeName !== this.name) { throw new Error(`Can not cast object of type '${typeName}' using '${this.name}'`); @@ -59,13 +79,13 @@ export function Type(this: any, config: any) { throw new Error(`Can not cast '${typeName}' to '${toTypeName}'`); } - return (getToFn(toTypeName) as any)(node, types); + return (this.getToFn(toTypeName) as any)(node, types); }; - this.from = (node: any, types: any) => { + from = (node: any, types: any) => { const typeName = getType(node); if (!this.castsFrom(typeName)) throw new Error(`Can not cast '${this.name}' from ${typeName}`); - return (getFromFn(typeName) as any)(node, types); + return (this.getFromFn(typeName) as any)(node, types); }; } diff --git a/src/plugins/expressions/common/expressions/interpreter_provider.ts b/src/plugins/expressions/public/interpreter_provider.ts similarity index 88% rename from src/plugins/expressions/common/expressions/interpreter_provider.ts rename to src/plugins/expressions/public/interpreter_provider.ts index 59ba1085a9d7d..cb84370ad69c5 100644 --- a/src/plugins/expressions/common/expressions/interpreter_provider.ts +++ b/src/plugins/expressions/public/interpreter_provider.ts @@ -21,11 +21,11 @@ import { clone, each, keys, last, mapValues, reduce, zipObject } from 'lodash'; // @ts-ignore -import { fromExpression, getByAlias, castProvider } from '@kbn/interpreter/common'; +import { fromExpression, getByAlias } from '@kbn/interpreter/common'; import { createError } from './create_error'; import { ExpressionAST, ExpressionFunctionAST, AnyExpressionFunction, ArgumentType } from './types'; -import { getType } from '../../common'; +import { getType } from './interpreter'; export { createError }; @@ -40,7 +40,30 @@ export type ExpressionInterpret = (ast: ExpressionAST, context?: any) => any; export function interpreterProvider(config: InterpreterConfig): ExpressionInterpret { const { functions, types } = config; const handlers = { ...config.handlers, types }; - const cast = castProvider(types); + + function cast(node: any, toTypeNames: any) { + // If you don't give us anything to cast to, you'll get your input back + if (!toTypeNames || toTypeNames.length === 0) return node; + + // No need to cast if node is already one of the valid types + const fromTypeName = getType(node); + if (toTypeNames.includes(fromTypeName)) return node; + + const fromTypeDef = types[fromTypeName]; + + for (let i = 0; i < toTypeNames.length; i++) { + // First check if the current type can cast to this type + if (fromTypeDef && fromTypeDef.castsTo(toTypeNames[i])) { + return fromTypeDef.to(node, toTypeNames[i], types); + } + + // If that isn't possible, check if this type can cast from the current type + const toTypeDef = types[toTypeNames[i]]; + if (toTypeDef && toTypeDef.castsFrom(fromTypeName)) return toTypeDef.from(node, types); + } + + throw new Error(`Can not cast '${fromTypeName}' to any of '${toTypeNames.join(', ')}'`); + } async function invokeChain(chainArr: ExpressionFunctionAST[], context: any): Promise { if (!chainArr.length) return context; diff --git a/src/plugins/expressions/public/mocks.ts b/src/plugins/expressions/public/mocks.ts index 56ff343e70ac4..9b47aa2b0cd97 100644 --- a/src/plugins/expressions/public/mocks.ts +++ b/src/plugins/expressions/public/mocks.ts @@ -16,10 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { Plugin } from '.'; +import { ExpressionsSetup, ExpressionsStart } from '.'; -export type Setup = jest.Mocked>; -export type Start = jest.Mocked>; +export type Setup = jest.Mocked; +export type Start = jest.Mocked; const createSetupContract = (): Setup => { const setupContract: Setup = { @@ -36,28 +36,18 @@ const createSetupContract = (): Setup => { types: { register: () => {}, } as any, + getExecutor: () => ({ + interpreter: { + interpretAst: () => {}, + }, + }), }, }; return setupContract; }; const createStartContract = (): Start => { - const startContract: Start = { - registerFunction: jest.fn(), - registerRenderer: jest.fn(), - registerType: jest.fn(), - __LEGACY: { - functions: { - register: () => {}, - } as any, - renderers: { - register: () => {}, - } as any, - types: { - register: () => {}, - } as any, - }, - }; + const startContract: Start = undefined; return startContract; }; diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts index b301981f58801..e67b503f301aa 100644 --- a/src/plugins/expressions/public/plugin.ts +++ b/src/plugins/expressions/public/plugin.ts @@ -19,29 +19,133 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { - ExpressionsService, - ExpressionsSetupContract, - ExpressionsStartContract, -} from './expressions/expressions_service'; + AnyExpressionFunction, + AnyExpressionType, + ExpressionInterpretWithHandlers, + ExpressionExecutor, +} from './types'; +import { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './registries'; +import { Setup as InspectorSetup, Start as InspectorStart } from '../../inspector/public'; +import { setCoreStart } from './services'; +import { clog as clogFunction } from './functions/clog'; +import { font as fontFunction } from './functions/font'; +import { kibana as kibanaFunction } from './functions/kibana'; +import { kibanaContext as kibanaContextFunction } from './functions/kibana_context'; +import { + boolean as booleanType, + datatable as datatableType, + error as errorType, + filter as filterType, + image as imageType, + nullType, + number as numberType, + pointseries, + range as rangeType, + render as renderType, + shape as shapeType, + string as stringType, + style as styleType, + kibanaContext as kibanaContextType, + kibanaDatatable as kibanaDatatableType, +} from './expression_types'; +import { interpreterProvider } from './interpreter_provider'; +import { createHandlers } from './create_handlers'; + +export interface ExpressionsSetupDeps { + inspector: InspectorSetup; +} + +export interface ExpressionsStartDeps { + inspector: InspectorStart; +} + +export interface ExpressionsSetup { + registerFunction: (fn: AnyExpressionFunction | (() => AnyExpressionFunction)) => void; + registerRenderer: (renderer: any) => void; + registerType: (type: () => AnyExpressionType) => void; + __LEGACY: { + functions: FunctionsRegistry; + renderers: RenderFunctionsRegistry; + types: TypesRegistry; + getExecutor: () => ExpressionExecutor; + }; +} + +export type ExpressionsStart = void; export class ExpressionsPublicPlugin - implements Plugin<{}, {}, ExpressionsSetupContract, ExpressionsStartContract> { - private readonly expressions = new ExpressionsService(); + implements + Plugin { + private readonly functions = new FunctionsRegistry(); + private readonly renderers = new RenderFunctionsRegistry(); + private readonly types = new TypesRegistry(); constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup): ExpressionsSetupContract { - const expressions = this.expressions.setup(); - return { - ...expressions, + public setup(core: CoreSetup, { inspector }: ExpressionsSetupDeps): ExpressionsSetup { + const { functions, renderers, types } = this; + + const registerFunction: ExpressionsSetup['registerFunction'] = fn => { + functions.register(fn); + }; + + registerFunction(clogFunction); + registerFunction(fontFunction); + registerFunction(kibanaFunction); + registerFunction(kibanaContextFunction); + + types.register(booleanType); + types.register(datatableType); + types.register(errorType); + types.register(filterType); + types.register(imageType); + types.register(nullType); + types.register(numberType); + types.register(pointseries); + types.register(rangeType); + types.register(renderType); + types.register(shapeType); + types.register(stringType); + types.register(styleType); + types.register(kibanaContextType); + types.register(kibanaDatatableType); + + // TODO: Refactor this function. + const getExecutor = () => { + const interpretAst: ExpressionInterpretWithHandlers = (ast, context, handlers) => { + const interpret = interpreterProvider({ + types: types.toJS(), + handlers: { ...handlers, ...createHandlers() }, + functions: functions.toJS(), + }); + return interpret(ast, context); + }; + const executor: ExpressionExecutor = { interpreter: { interpretAst } }; + return executor; }; - } - public start(core: CoreStart): ExpressionsStartContract { - const expressions = this.expressions.start(); - return { - ...expressions, + const setup: ExpressionsSetup = { + registerFunction, + registerRenderer: (renderer: any) => { + renderers.register(renderer); + }, + registerType: type => { + types.register(type); + }, + __LEGACY: { + functions, + renderers, + types, + getExecutor, + }, }; + + return setup; + } + + public start(core: CoreStart, { inspector }: ExpressionsStartDeps): ExpressionsStart { + setCoreStart(core); } + public stop() {} } diff --git a/src/plugins/expressions/public/registries/function_registry.ts b/src/plugins/expressions/public/registries/function_registry.ts new file mode 100644 index 0000000000000..c15b7ceb0d217 --- /dev/null +++ b/src/plugins/expressions/public/registries/function_registry.ts @@ -0,0 +1,127 @@ +/* + * 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 max-classes-per-file */ + +import { AnyExpressionFunction, FunctionHandlers } from '../types/functions'; +import { ExpressionValue } from '../types/types'; +import { ArgumentType } from '../types'; +import { Registry } from './registry'; + +export class FunctionParameter { + name: string; + required: boolean; + help: string; + types: string[]; + default: any; + aliases: string[]; + multi: boolean; + resolve: boolean; + options: any[]; + + constructor(name: string, arg: ArgumentType) { + const { required, help, types, aliases, multi, resolve, options } = arg; + + if (name === '_') { + throw Error('Arg names must not be _. Use it in aliases instead.'); + } + + this.name = name; + this.required = !!required; + this.help = help || ''; + this.types = types || []; + this.default = arg.default; + this.aliases = aliases || []; + this.multi = !!multi; + this.resolve = resolve == null ? true : resolve; + this.options = options || []; + } + + accepts(type: string) { + if (!this.types.length) return true; + return this.types.indexOf(type) > -1; + } +} + +export class Function { + /** + * Name of function + */ + name: string; + + /** + * Aliases that can be used instead of `name`. + */ + aliases: string[]; + + /** + * Return type of function. This SHOULD be supplied. We use it for UI + * and autocomplete hinting. We may also use it for optimizations in + * the future. + */ + type: string; + + /** + * Function to run function (context, args) + */ + fn: ( + input: ExpressionValue, + params: Record, + handlers: FunctionHandlers + ) => ExpressionValue; + + /** + * A short help text. + */ + help: string; + + args: Record = {}; + + context: { types?: string[] }; + + constructor(functionDefinition: AnyExpressionFunction) { + const { name, type, aliases, fn, help, args, context } = functionDefinition; + + this.name = name; + this.type = type; + this.aliases = aliases || []; + this.fn = (input, params, handlers) => Promise.resolve(fn(input, params, handlers)); + this.help = help || ''; + this.context = context || {}; + + for (const [key, arg] of Object.entries(args || {})) { + this.args[key] = new FunctionParameter(key, arg); + } + } + + accepts = (type: string): boolean => { + // If you don't tell us about context, we'll assume you don't care what you get. + if (!this.context.types) return true; + return this.context.types.indexOf(type) > -1; + }; +} + +export class FunctionsRegistry extends Registry { + register(functionDefinition: AnyExpressionFunction | (() => AnyExpressionFunction)) { + const fn = new Function( + typeof functionDefinition === 'object' ? functionDefinition : functionDefinition() + ); + this.set(fn.name, fn); + } +} diff --git a/src/plugins/expressions/common/expressions/index.ts b/src/plugins/expressions/public/registries/index.ts similarity index 80% rename from src/plugins/expressions/common/expressions/index.ts rename to src/plugins/expressions/public/registries/index.ts index 8c8dd7eb26ca5..16c8d8fc4c93a 100644 --- a/src/plugins/expressions/common/expressions/index.ts +++ b/src/plugins/expressions/public/registries/index.ts @@ -17,7 +17,6 @@ * under the License. */ -export * from './types'; -export { Type } from './interpreter'; -export { interpreterProvider } from './interpreter_provider'; -export { serializeProvider, getType } from './serialize_provider'; +export * from './type_registry'; +export * from './function_registry'; +export * from './render_registry'; diff --git a/src/plugins/expressions/public/registries/registry.ts b/src/plugins/expressions/public/registries/registry.ts new file mode 100644 index 0000000000000..fe149116fbf14 --- /dev/null +++ b/src/plugins/expressions/public/registries/registry.ts @@ -0,0 +1,42 @@ +/* + * 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. + */ + +export class Registry { + private data: Record = {}; + + set(id: string, item: T) { + this.data[id] = item; + } + + get(id: string): T | null { + return this.data[id] || null; + } + + toJS(): Record { + return { ...this.data }; + } + + toArray(): T[] { + return Object.values(this.data); + } + + reset() { + this.data = {}; + } +} diff --git a/src/plugins/expressions/public/registries/render_registry.ts b/src/plugins/expressions/public/registries/render_registry.ts new file mode 100644 index 0000000000000..6fd48f5f0c6af --- /dev/null +++ b/src/plugins/expressions/public/registries/render_registry.ts @@ -0,0 +1,83 @@ +/* + * 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 max-classes-per-file */ + +import { Registry } from './registry'; + +export interface ExpressionRenderDefinition { + name: string; + displayName: string; + help?: string; + validate?: () => void | Error; + reuseDomNode: boolean; + render: (domNode: HTMLElement, config: Config, handlers: any) => Promise; +} + +class ExpressionRenderFunction { + /** + * This must match the name of the function that is used to create the `type: render` object. + */ + name: string; + + /** + * Use this to set a more friendly name. + */ + displayName: string; + + /** + * A sentence or few about what this element does. + */ + help: string; + + /** + * Used to validate the data before calling the render function. + */ + validate: () => void | Error; + + /** + * Tell the renderer if the dom node should be reused, it's recreated each time by default. + */ + reuseDomNode: boolean; + + /** + * The function called to render the data. + */ + render: (domNode: HTMLElement, config: any, handlers: any) => Promise; + + constructor(config: ExpressionRenderDefinition) { + const { name, displayName, help, validate, reuseDomNode, render } = config; + + this.name = name; + this.displayName = displayName || name; + this.help = help || ''; + this.validate = validate || (() => {}); + this.reuseDomNode = Boolean(reuseDomNode); + this.render = render; + } +} + +export class RenderFunctionsRegistry extends Registry { + register(definition: ExpressionRenderDefinition | (() => ExpressionRenderDefinition)) { + const renderFunction = new ExpressionRenderFunction( + typeof definition === 'object' ? definition : definition() + ); + this.set(renderFunction.name, renderFunction); + } +} diff --git a/src/plugins/expressions/public/registries/type_registry.ts b/src/plugins/expressions/public/registries/type_registry.ts new file mode 100644 index 0000000000000..bcc801213582e --- /dev/null +++ b/src/plugins/expressions/public/registries/type_registry.ts @@ -0,0 +1,29 @@ +/* + * 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 { Registry } from './registry'; +import { Type } from '../interpreter'; +import { AnyExpressionType } from '../types'; + +export class TypesRegistry extends Registry { + register(typeDefinition: AnyExpressionType | (() => AnyExpressionType)) { + const type = new Type(typeof typeDefinition === 'object' ? typeDefinition : typeDefinition()); + this.set(type.name, type); + } +} diff --git a/src/plugins/expressions/common/expressions/serialize_provider.ts b/src/plugins/expressions/public/serialize_provider.ts similarity index 84% rename from src/plugins/expressions/common/expressions/serialize_provider.ts rename to src/plugins/expressions/public/serialize_provider.ts index 1bd06a38a2560..449d47c183dbb 100644 --- a/src/plugins/expressions/common/expressions/serialize_provider.ts +++ b/src/plugins/expressions/public/serialize_provider.ts @@ -18,15 +18,7 @@ */ import { get, identity } from 'lodash'; - -export function getType(node: any) { - if (node == null) return 'null'; - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type property'); - return node.type; - } - return typeof node; -} +import { getType } from './interpreter'; export function serializeProvider(types: any) { return { diff --git a/src/plugins/expressions/common/expressions/types/index.ts b/src/plugins/expressions/public/services.ts similarity index 54% rename from src/plugins/expressions/common/expressions/types/index.ts rename to src/plugins/expressions/public/services.ts index baa0850cfd76c..a1a42aa85e670 100644 --- a/src/plugins/expressions/common/expressions/types/index.ts +++ b/src/plugins/expressions/public/services.ts @@ -17,29 +17,19 @@ * under the License. */ -export { ArgumentType } from './arguments'; -export { - TypeToString, - KnownTypeToString, - TypeString, - UnmappedTypeStrings, - UnwrapPromise, -} from './common'; -export { ExpressionFunction, AnyExpressionFunction, FunctionHandlers } from './functions'; -export { ExpressionType, AnyExpressionType } from './types'; -export * from '../expression_types'; +import { createKibanaUtilsCore, createGetterSetter } from '../../kibana_utils/public'; +import { ExpressionInterpreter } from './types'; +import { Start as IInspector } from '../../inspector/public'; +import { ExpressionsSetup } from './plugin'; -export type ExpressionArgAST = string | boolean | number | ExpressionAST; +export const { getCoreStart, setCoreStart, savedObjects } = createKibanaUtilsCore(); -export interface ExpressionFunctionAST { - type: 'function'; - function: string; - arguments: { - [key: string]: ExpressionArgAST[]; - }; -} +export const [getInspector, setInspector] = createGetterSetter('Inspector'); -export interface ExpressionAST { - type: 'expression'; - chain: ExpressionFunctionAST[]; -} +export const [getInterpreter, setInterpreter] = createGetterSetter( + 'Interpreter' +); + +export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter< + ExpressionsSetup['__LEGACY']['renderers'] +>('Renderers registry'); diff --git a/src/plugins/expressions/common/expressions/types/arguments.ts b/src/plugins/expressions/public/types/arguments.ts similarity index 100% rename from src/plugins/expressions/common/expressions/types/arguments.ts rename to src/plugins/expressions/public/types/arguments.ts diff --git a/src/plugins/expressions/common/expressions/types/common.ts b/src/plugins/expressions/public/types/common.ts similarity index 100% rename from src/plugins/expressions/common/expressions/types/common.ts rename to src/plugins/expressions/public/types/common.ts diff --git a/src/plugins/expressions/common/expressions/types/functions.ts b/src/plugins/expressions/public/types/functions.ts similarity index 100% rename from src/plugins/expressions/common/expressions/types/functions.ts rename to src/plugins/expressions/public/types/functions.ts diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts new file mode 100644 index 0000000000000..2adb0d7794159 --- /dev/null +++ b/src/plugins/expressions/public/types/index.ts @@ -0,0 +1,142 @@ +/* + * 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 { Filter } from '@kbn/es-query'; +import { ExpressionInterpret } from '../interpreter_provider'; +import { TimeRange } from '../../../data/public'; +import { Adapters } from '../../../inspector/public'; +import { Query } from '../../../data/public'; +import { ExpressionAST } from '../../../expressions/public'; +import { ExpressionArgAST } from '../../../../plugins/expressions/public'; + +export { ArgumentType } from './arguments'; +export { + TypeToString, + KnownTypeToString, + TypeString, + UnmappedTypeStrings, + UnwrapPromise, + SerializedFieldFormat, +} from './common'; + +export { ExpressionFunction, AnyExpressionFunction, FunctionHandlers } from './functions'; +export { ExpressionType, AnyExpressionType } from './types'; + +export * from './style'; + +export type ExpressionArgAST = string | boolean | number | ExpressionAST; + +export interface ExpressionFunctionAST { + type: 'function'; + function: string; + arguments: { + [key: string]: ExpressionArgAST[]; + }; +} + +export interface ExpressionAST { + type: 'expression'; + chain: ExpressionFunctionAST[]; +} + +export type ExpressionInterpretWithHandlers = ( + ast: Parameters[0], + context: Parameters[1], + handlers: IInterpreterHandlers +) => ReturnType; + +export interface ExpressionInterpreter { + interpretAst: ExpressionInterpretWithHandlers; +} + +export interface ExpressionExecutor { + interpreter: ExpressionInterpreter; +} + +export type RenderId = number; +export type Data = any; +export type event = any; +export type Context = object; + +export interface SearchContext { + type: 'kibana_context'; + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; +} + +export type IGetInitialContext = () => SearchContext | Context; + +export interface IExpressionLoaderParams { + searchContext?: SearchContext; + context?: Context; + variables?: Record; + disableCaching?: boolean; + customFunctions?: []; + customRenderers?: []; + extraHandlers?: Record; +} + +export interface IInterpreterHandlers { + getInitialContext: IGetInitialContext; + inspectorAdapters?: Adapters; + abortSignal?: AbortSignal; +} + +export interface IInterpreterRenderHandlers { + /** + * Done increments the number of rendering successes + */ + done: () => void; + onDestroy: (fn: () => void) => void; + reload: () => void; + update: (params: any) => void; + event: (event: event) => void; +} + +export interface IInterpreterRenderFunction { + name: string; + displayName: string; + help: string; + validate: () => void; + reuseDomNode: boolean; + render: (domNode: Element, data: T, handlers: IInterpreterRenderHandlers) => void | Promise; +} + +export interface IInterpreterErrorResult { + type: 'error'; + error: { message: string; name: string; stack: string }; +} + +export interface IInterpreterSuccessResult { + type: string; + as?: string; + value?: unknown; + error?: unknown; +} + +export type IInterpreterResult = IInterpreterSuccessResult & IInterpreterErrorResult; + +export interface IInterpreter { + interpretAst( + ast: ExpressionAST, + context: Context, + handlers: IInterpreterHandlers + ): Promise; +} diff --git a/src/plugins/expressions/public/types/style.ts b/src/plugins/expressions/public/types/style.ts new file mode 100644 index 0000000000000..9f919223d558c --- /dev/null +++ b/src/plugins/expressions/public/types/style.ts @@ -0,0 +1,137 @@ +/* + * 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 { FontLabel } from '../fonts'; + +/** + * Enum of supported CSS `background-repeat` properties. + */ +export enum BackgroundRepeat { + REPEAT = 'repeat', + REPEAT_NO = 'no-repeat', + REPEAT_X = 'repeat-x', + REPEAT_Y = 'repeat-y', + ROUND = 'round', + SPACE = 'space', +} + +/** + * Enum of supported CSS `background-size` properties. + */ +export enum BackgroundSize { + AUTO = 'auto', + CONTAIN = 'contain', + COVER = 'cover', +} + +/** + * Enum of supported CSS `font-style` properties. + */ +export enum FontStyle { + ITALIC = 'italic', + NORMAL = 'normal', +} + +/** + * Enum of supported CSS `font-weight` properties. + */ +export enum FontWeight { + NORMAL = 'normal', + BOLD = 'bold', + BOLDER = 'bolder', + LIGHTER = 'lighter', + ONE = '100', + TWO = '200', + THREE = '300', + FOUR = '400', + FIVE = '500', + SIX = '600', + SEVEN = '700', + EIGHT = '800', + NINE = '900', +} + +/** + * Enum of supported CSS `overflow` properties. + */ +export enum Overflow { + AUTO = 'auto', + HIDDEN = 'hidden', + SCROLL = 'scroll', + VISIBLE = 'visible', +} + +/** + * Enum of supported CSS `text-align` properties. + */ +export enum TextAlignment { + CENTER = 'center', + JUSTIFY = 'justify', + LEFT = 'left', + RIGHT = 'right', +} + +/** + * Enum of supported CSS `text-decoration` properties. + */ +export enum TextDecoration { + NONE = 'none', + UNDERLINE = 'underline', +} + +/** + * Represents the various style properties that can be applied to an element. + */ +export interface CSSStyle { + color?: string; + fill?: string; + fontFamily?: FontLabel; + fontSize?: string; + fontStyle?: FontStyle; + fontWeight?: FontWeight; + lineHeight?: number | string; + textAlign?: TextAlignment; + textDecoration?: TextDecoration; +} + +/** + * Represents an object containing style information for a Container. + */ +export interface ContainerStyle { + border: string | null; + borderRadius: string | null; + padding: string | null; + backgroundColor: string | null; + backgroundImage: string | null; + backgroundSize: BackgroundSize; + backgroundRepeat: BackgroundRepeat; + opacity: number | null; + overflow: Overflow; +} + +/** + * An object that represents style information, typically CSS. + */ +export interface ExpressionTypeStyle { + type: 'style'; + spec: CSSStyle; + css: string; +} + +export type Style = ExpressionTypeStyle; diff --git a/src/plugins/expressions/common/expressions/types/types.ts b/src/plugins/expressions/public/types/types.ts similarity index 99% rename from src/plugins/expressions/common/expressions/types/types.ts rename to src/plugins/expressions/public/types/types.ts index 59297e922d313..e7b30d24fa6eb 100644 --- a/src/plugins/expressions/common/expressions/types/types.ts +++ b/src/plugins/expressions/public/types/types.ts @@ -51,6 +51,7 @@ export interface ExpressionType< to?: { [type: string]: ExpressionValueConverter; }; + help?: string; } export type AnyExpressionType = ExpressionType; diff --git a/src/plugins/kibana_react/public/context/context.tsx b/src/plugins/kibana_react/public/context/context.tsx index 630348017106a..cbae5c4638ca2 100644 --- a/src/plugins/kibana_react/public/context/context.tsx +++ b/src/plugins/kibana_react/public/context/context.tsx @@ -42,11 +42,11 @@ export const useKibana = (): KibanaReactContextValue< export const withKibana = }>( type: React.ComponentType ): React.FC> => { - const enhancedType: React.FC> = (props: Omit) => { + const EnhancedType: React.FC> = (props: Omit) => { const kibana = useKibana(); return React.createElement(type, { ...props, kibana } as Props); }; - return enhancedType; + return EnhancedType; }; export const UseKibana: React.FC<{ @@ -69,7 +69,7 @@ export const createKibanaReactContext = ( const oldValue = useKibana(); const { value: newValue } = useMemo( () => createKibanaReactContext({ ...services, ...oldValue.services, ...newServices }), - Object.keys(services) + [services, oldValue, newServices] ); return createElement(context.Provider as React.ComponentType, { value: newValue, diff --git a/src/plugins/kibana_react/public/context/index.ts b/src/plugins/kibana_react/public/context/index.ts index 444ef343cd7d2..c7c0693a89f2b 100644 --- a/src/plugins/kibana_react/public/context/index.ts +++ b/src/plugins/kibana_react/public/context/index.ts @@ -25,4 +25,4 @@ export { withKibana, UseKibana, } from './context'; -export { KibanaReactContext, KibanaReactContextValue } from './types'; +export { KibanaReactContext, KibanaReactContextValue, KibanaServices } from './types'; diff --git a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx index badf568ccc193..0879b0cb3f36a 100644 --- a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx +++ b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.test.tsx @@ -106,7 +106,7 @@ describe('useUiSetting', () => { }); describe('useUiSetting$', () => { - const TestConsumer: React.FC<{ + const TestConsumerX: React.FC<{ setting: string; newValue?: string; }> = ({ setting, newValue = '' }) => { @@ -126,7 +126,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -143,7 +143,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -159,7 +159,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); @@ -174,7 +174,7 @@ describe('useUiSetting$', () => { ReactDOM.render( - + , container ); diff --git a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts index ddc8b2a684728..295515bfa51af 100644 --- a/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts +++ b/src/plugins/kibana_react/public/ui_settings/use_ui_setting.ts @@ -65,6 +65,7 @@ export const useUiSetting$ = (key: string, defaultValue?: T): [T, Setter] const observable$ = useMemo(() => services.uiSettings!.get$(key, defaultValue), [ key, defaultValue, + services.uiSettings, ]); const value = useObservable(observable$, services.uiSettings!.get(key, defaultValue)); const set = useCallback((newValue: T) => services.uiSettings!.set(key, newValue), [key]); diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 30b49e5a82e9c..3aaa6d28a9f64 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -18,9 +18,11 @@ */ export * from './core'; +export * from './errors'; export * from './store'; export * from './parse'; export * from './render_complete'; +export * from './store'; export * from './errors'; export * from './field_mapping'; export * from './storage'; diff --git a/src/plugins/kibana_utils/public/store/react.ts b/src/plugins/kibana_utils/public/store/react.ts index d561f9bb3cf34..00861b2b0b8fe 100644 --- a/src/plugins/kibana_utils/public/store/react.ts +++ b/src/plugins/kibana_utils/public/store/react.ts @@ -86,10 +86,12 @@ export const createContext = < comparator?: Comparator ): Result => { const { state$, get } = useStore(); + /* eslint-disable react-hooks/exhaustive-deps */ const [observable$, unsubscribe] = useMemo( () => observableSelector(get(), state$, selector, comparator), [state$] ); + /* eslint-enable react-hooks/exhaustive-deps */ useLayoutEffect(() => unsubscribe, [observable$, unsubscribe]); const value = useObservable(observable$, selector(get())); return value; diff --git a/src/plugins/visualizations/kibana.json b/src/plugins/visualizations/kibana.json new file mode 100644 index 0000000000000..cf79ce17293d6 --- /dev/null +++ b/src/plugins/visualizations/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "visualizations", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": [ + "expressions" + ] +} diff --git a/src/plugins/visualizations/public/expression_functions/range.ts b/src/plugins/visualizations/public/expression_functions/range.ts new file mode 100644 index 0000000000000..27c3654e2182a --- /dev/null +++ b/src/plugins/visualizations/public/expression_functions/range.ts @@ -0,0 +1,61 @@ +/* + * 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 { ExpressionFunction, KibanaDatatable, Range } from '../../../expressions/public'; + +const name = 'range'; + +type Context = KibanaDatatable | null; + +interface Arguments { + from: number; + to: number; +} + +export const range = (): ExpressionFunction => ({ + name, + help: i18n.translate('visualizations.function.range.help', { + defaultMessage: 'Generates range object', + }), + type: 'range', + args: { + from: { + types: ['number'], + help: i18n.translate('visualizations.function.range.from.help', { + defaultMessage: 'Start of range', + }), + required: true, + }, + to: { + types: ['number'], + help: i18n.translate('visualizations.function.range.to.help', { + defaultMessage: 'End of range', + }), + required: true, + }, + }, + fn: (context, args) => { + return { + type: 'range', + from: args.from, + to: args.to, + }; + }, +}); diff --git a/src/plugins/visualizations/public/expression_functions/vis_dimension.ts b/src/plugins/visualizations/public/expression_functions/vis_dimension.ts new file mode 100644 index 0000000000000..4ad73ef504874 --- /dev/null +++ b/src/plugins/visualizations/public/expression_functions/vis_dimension.ts @@ -0,0 +1,89 @@ +/* + * 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 { ExpressionFunction, KibanaDatatable } from '../../../expressions/public'; + +const name = 'visdimension'; + +type Context = KibanaDatatable | null; + +interface Arguments { + accessor: string | number; + format?: string; + formatParams?: string; +} + +type Return = any; + +export const visDimension = (): ExpressionFunction => ({ + name: 'visdimension', + help: i18n.translate('visualizations.function.visDimension.help', { + defaultMessage: 'Generates visConfig dimension object', + }), + type: 'vis_dimension', + context: { + types: ['kibana_datatable'], + }, + args: { + accessor: { + types: ['string', 'number'], + aliases: ['_'], + help: i18n.translate('visualizations.function.visDimension.accessor.help', { + defaultMessage: 'Column in your dataset to use (either column index or column name)', + }), + }, + format: { + types: ['string'], + default: 'string', + help: i18n.translate('visualizations.function.visDimension.format.help', { + defaultMessage: 'Format', + }), + }, + formatParams: { + types: ['string'], + default: '"{}"', + help: i18n.translate('visualizations.function.visDimension.formatParams.help', { + defaultMessage: 'Format params', + }), + }, + }, + fn: (context, args) => { + const accessor = + typeof args.accessor === 'number' + ? args.accessor + : context!.columns.find(c => c.id === args.accessor); + if (accessor === undefined) { + throw new Error( + i18n.translate('visualizations.function.visDimension.error.accessor', { + defaultMessage: 'Column name provided is invalid', + }) + ); + } + + return { + type: 'vis_dimension', + accessor, + format: { + id: args.format, + params: JSON.parse(args.formatParams!), + }, + }; + }, +}); diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index d8f7b5091eb8f..2b4b10c8329a3 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -17,4 +17,13 @@ * under the License. */ +import { PluginInitializerContext } from '../../../core/public'; +import { VisualizationsPublicPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new VisualizationsPublicPlugin(initializerContext); +} + +export { VisualizationsPublicPlugin as Plugin }; +export * from './plugin'; export * from './types'; diff --git a/src/plugins/visualizations/public/mocks.ts b/src/plugins/visualizations/public/mocks.ts new file mode 100644 index 0000000000000..af7688d019f65 --- /dev/null +++ b/src/plugins/visualizations/public/mocks.ts @@ -0,0 +1,37 @@ +/* + * 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 { VisualizationsSetup, VisualizationsStart } from '.'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = undefined; + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = undefined; + return startContract; +}; + +export const expressionsPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/src/plugins/visualizations/public/plugin.ts b/src/plugins/visualizations/public/plugin.ts new file mode 100644 index 0000000000000..cceb63122820d --- /dev/null +++ b/src/plugins/visualizations/public/plugin.ts @@ -0,0 +1,59 @@ +/* + * 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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; +import { ExpressionsSetup, ExpressionsStart } from '../../expressions/public'; +import { range as rangeExpressionFunction } from './expression_functions/range'; +import { visDimension as visDimensionExpressionFunction } from './expression_functions/vis_dimension'; + +export interface VisualizationsSetupDeps { + expressions: ExpressionsSetup; +} + +export interface VisualizationsStartDeps { + expressions: ExpressionsStart; +} + +export type VisualizationsSetup = void; + +export type VisualizationsStart = void; + +export class VisualizationsPublicPlugin + implements + Plugin< + VisualizationsSetup, + VisualizationsStart, + VisualizationsSetupDeps, + VisualizationsStartDeps + > { + constructor(initializerContext: PluginInitializerContext) {} + + public setup(core: CoreSetup, { expressions }: VisualizationsSetupDeps): VisualizationsSetup { + expressions.registerFunction(rangeExpressionFunction); + expressions.registerFunction(visDimensionExpressionFunction); + + return undefined; + } + + public start(core: CoreStart, { expressions }: VisualizationsStartDeps): VisualizationsStart { + return undefined; + } + + public stop() {} +} diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js index d909b51e94ac0..bc70339bd1a66 100644 --- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js +++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js @@ -61,8 +61,7 @@ export default function ({ getService }) { aggregatable: true, name: 'baz.keyword', readFromDocValues: true, - parent: 'baz', - subType: 'multi', + subType: { multi: { parent: 'baz' } } }, { type: 'number', @@ -72,6 +71,21 @@ export default function ({ getService }) { name: 'foo', readFromDocValues: true, }, + { + aggregatable: true, + esTypes: [ + 'keyword' + ], + name: 'nestedField.child', + readFromDocValues: true, + searchable: true, + subType: { + nested: { + path: 'nestedField' + } + }, + type: 'string', + }, ], }) .then(ensureFieldsAreSorted)); @@ -124,8 +138,7 @@ export default function ({ getService }) { aggregatable: true, name: 'baz.keyword', readFromDocValues: true, - parent: 'baz', - subType: 'multi', + subType: { multi: { parent: 'baz' } } }, { aggregatable: false, @@ -142,6 +155,21 @@ export default function ({ getService }) { name: 'foo', readFromDocValues: true, }, + { + aggregatable: true, + esTypes: [ + 'keyword' + ], + name: 'nestedField.child', + readFromDocValues: true, + searchable: true, + subType: { + nested: { + path: 'nestedField' + } + }, + type: 'string', + }, ], }) .then(ensureFieldsAreSorted)); diff --git a/test/api_integration/apis/suggestions/suggestions.js b/test/api_integration/apis/suggestions/suggestions.js index 80b6febb81d7b..abacf13c7aa06 100644 --- a/test/api_integration/apis/suggestions/suggestions.js +++ b/test/api_integration/apis/suggestions/suggestions.js @@ -22,8 +22,14 @@ export default function ({ getService }) { const supertest = getService('supertest'); describe('Suggestions API', function () { - before(() => esArchiver.load('index_patterns/basic_index')); - after(() => esArchiver.unload('index_patterns/basic_index')); + before(async () => { + await esArchiver.load('index_patterns/basic_index'); + await esArchiver.load('index_patterns/basic_kibana'); + }); + after(async () => { + await esArchiver.unload('index_patterns/basic_index'); + await esArchiver.unload('index_patterns/basic_kibana'); + }); it('should return 200 with special characters', () => ( supertest @@ -34,5 +40,15 @@ export default function ({ getService }) { }) .expect(200) )); + + it('should support nested fields', () => ( + supertest + .post('/api/kibana/suggestions/values/basic_index') + .send({ + field: 'nestedField.child', + query: 'nes' + }) + .expect(200, ['nestedValue']) + )); }); } diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz index edae3bfdaa2ea..ec085ec813240 100644 Binary files a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz and b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/data.json.gz differ diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json index 5d0d426010953..338b31dc681ae 100644 --- a/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json +++ b/test/api_integration/fixtures/es_archiver/index_patterns/basic_index/mappings.json @@ -24,8 +24,18 @@ }, "foo": { "type": "long" + }, + "nestedField": { + "type": "nested", + "properties": { + "child": { + "type": "keyword" + } + } } } } } -} \ No newline at end of file +} + + diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/data.json b/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/data.json new file mode 100644 index 0000000000000..54276b59dcc23 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/data.json @@ -0,0 +1,15 @@ +{ + "type": "doc", + "value": { + "index": ".kibana", + "id": "index-pattern:91200a00-9efd-11e7-acb3-3dab96693fab", + "source": { + "type": "index-pattern", + "updated_at": "2017-09-21T18:49:16.270Z", + "index-pattern": { + "title": "basic_index", + "fields": "[{\"name\":\"bar\",\"type\":\"boolean\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"baz\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"baz.keyword\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"foo\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"nestedField.child\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"nested\":{\"path\":\"nestedField\"}}}]" + } + } + } +} diff --git a/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/mappings.json b/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/mappings.json new file mode 100644 index 0000000000000..99264d7ebbff8 --- /dev/null +++ b/test/api_integration/fixtures/es_archiver/index_patterns/basic_kibana/mappings.json @@ -0,0 +1,253 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + }, + "mappings": { + "dynamic": "strict", + "properties": { + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "defaultIndex": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "namespace": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchId": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + } + } +} diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 6c7170d6b2168..9d3f95e28942a 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -26,6 +26,7 @@ export default function ({ getService, getPageObjects }) { const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); const filterBar = getService('filterBar'); + const queryBar = getService('queryBar'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { defaultIndex: 'logstash-*', @@ -154,6 +155,26 @@ export default function ({ getService, getPageObjects }) { }); }); + describe('nested query', () => { + + before(async () => { + log.debug('setAbsoluteRangeForAnotherQuery'); + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.waitUntilSearchingHasFinished(); + }); + + it('should support querying on nested fields', async function () { + await queryBar.setQuery('nestedField:{ child: nestedValue }'); + await queryBar.submitQuery(); + await retry.try(async function () { + expect(await PageObjects.discover.getHitCount()).to.be( + '1' + ); + }); + }); + + }); + describe('filter editor', function () { it('should add a phrases filter', async function () { await filterBar.addFilter('extension.raw', 'is one of', 'jpg'); diff --git a/test/functional/fixtures/es_archiver/discover/data.json.gz b/test/functional/fixtures/es_archiver/discover/data.json.gz index c607f211d37c2..047d890f6d410 100644 Binary files a/test/functional/fixtures/es_archiver/discover/data.json.gz and b/test/functional/fixtures/es_archiver/discover/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz b/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz index 2cef071738526..a212c34e2ead6 100644 Binary files a/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz and b/test/functional/fixtures/es_archiver/logstash_functional/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/logstash_functional/mappings.json b/test/functional/fixtures/es_archiver/logstash_functional/mappings.json index dcfafa2612c5b..010abff9cf6a9 100644 --- a/test/functional/fixtures/es_archiver/logstash_functional/mappings.json +++ b/test/functional/fixtures/es_archiver/logstash_functional/mappings.json @@ -153,6 +153,14 @@ } } }, + "nestedField": { + "type": "nested", + "properties": { + "child": { + "type": "keyword" + } + } + }, "phpmemory": { "type": "long" }, @@ -518,6 +526,14 @@ } } }, + "nestedField": { + "type": "nested", + "properties": { + "child": { + "type": "keyword" + } + } + }, "phpmemory": { "type": "long" }, @@ -883,6 +899,14 @@ } } }, + "nestedField": { + "type": "nested", + "properties": { + "child": { + "type": "keyword" + } + } + }, "phpmemory": { "type": "long" }, @@ -1091,4 +1115,4 @@ } } } -} \ No newline at end of file +} diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 5ebec6bde6ebb..201354fff48ef 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -75,13 +75,23 @@ async function attemptToCreateCommand( case 'chrome': { const chromeCapabilities = Capabilities.chrome(); const chromeOptions = [ - 'disable-translate', - 'new-window', + // Disables the sandbox for all process types that are normally sandboxed. 'no-sandbox', + // Launches URL in new browser window. + 'new-window', + // By default, file:// URIs cannot read other file:// URIs. This is an override for developers who need the old behavior for testing. 'allow-file-access-from-files', + // Use fake device for Media Stream to replace actual camera and microphone. 'use-fake-device-for-media-stream', + // Bypass the media stream infobar by selecting the default device for media streams (e.g. WebRTC). Works with --use-fake-device-for-media-stream. 'use-fake-ui-for-media-stream', ]; + if (process.platform === 'linux') { + // The /dev/shm partition is too small in certain VM environments, causing + // Chrome to fail or crash. Use this flag to work-around this issue + // (a temporary directory will always be used to create anonymous shared memory files). + chromeOptions.push('disable-dev-shm-usage'); + } if (headlessBrowser === '1') { // Use --disable-gpu to avoid an error from a missing Mesa library, as per // See: https://chromium.googlesource.com/chromium/src/+/lkgr/headless/README.md diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 274cca695b535..c6bddb69aa570 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -1,12 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi - -export TEST_BROWSER_HEADLESS=1 +source test/scripts/jenkins_test_setup.sh if [[ -z "$IS_PIPELINE_JOB" ]] ; then yarn run grunt functionalTests:ensureAllTestsInCiGroup; diff --git a/test/scripts/jenkins_firefox_smoke.sh b/test/scripts/jenkins_firefox_smoke.sh index 69cedc9657340..9a31f5f43d224 100755 --- a/test/scripts/jenkins_firefox_smoke.sh +++ b/test/scripts/jenkins_firefox_smoke.sh @@ -1,10 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi +source test/scripts/jenkins_test_setup.sh if [[ -z "$IS_PIPELINE_JOB" ]] ; then node scripts/build --debug --oss; diff --git a/test/scripts/jenkins_test_setup.sh b/test/scripts/jenkins_test_setup.sh new file mode 100644 index 0000000000000..e2dd0bc276bb6 --- /dev/null +++ b/test/scripts/jenkins_test_setup.sh @@ -0,0 +1,20 @@ +set -e + +function post_work() { + set +e + if [[ -z "$IS_PIPELINE_JOB" ]] ; then + node "$KIBANA_DIR/scripts/report_failed_tests" + fi + + if [[ -z "$REMOVE_KIBANA_INSTALL_DIR" && -z "$KIBANA_INSTALL_DIR" && -d "$KIBANA_INSTALL_DIR" ]]; then + rm -rf "$REMOVE_KIBANA_INSTALL_DIR" + fi +} + +trap 'post_work' EXIT + +export TEST_BROWSER_HEADLESS=1 + +if [[ -n "$IS_PIPELINE_JOB" ]] ; then + source src/dev/ci_setup/setup_env.sh +fi diff --git a/test/scripts/jenkins_visual_regression.sh b/test/scripts/jenkins_visual_regression.sh index fdda24423d977..9ca1c0f08d2c9 100755 --- a/test/scripts/jenkins_visual_regression.sh +++ b/test/scripts/jenkins_visual_regression.sh @@ -1,11 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi - +source test/scripts/jenkins_test_setup.sh source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" if [[ -z "$IS_PIPELINE_JOB" ]] ; then @@ -22,8 +17,6 @@ else export KIBANA_INSTALL_DIR="$destDir" fi -export TEST_BROWSER_HEADLESS=1 - checks-reporter-with-killswitch "Kibana visual regression tests" \ yarn run percy exec -t 500 \ node scripts/functional_tests \ diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index 0b9666b33deca..fba05f8f252d7 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -1,12 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi - -export TEST_BROWSER_HEADLESS=1 +source test/scripts/jenkins_test_setup.sh if [[ -z "$IS_PIPELINE_JOB" ]] ; then echo " -> Ensuring all functional tests are in a ciGroup" diff --git a/test/scripts/jenkins_xpack_firefox_smoke.sh b/test/scripts/jenkins_xpack_firefox_smoke.sh index 18ed468de1317..43220459bcb97 100755 --- a/test/scripts/jenkins_xpack_firefox_smoke.sh +++ b/test/scripts/jenkins_xpack_firefox_smoke.sh @@ -1,10 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi +source test/scripts/jenkins_test_setup.sh if [[ -z "$IS_PIPELINE_JOB" ]] ; then node scripts/build --debug --no-oss; @@ -21,8 +17,6 @@ else export KIBANA_INSTALL_DIR="$destDir" fi -export TEST_BROWSER_HEADLESS=1 - cd "$XPACK_DIR" checks-reporter-with-killswitch "X-Pack firefox smoke test" \ diff --git a/test/scripts/jenkins_xpack_visual_regression.sh b/test/scripts/jenkins_xpack_visual_regression.sh index 0f3275d45f13b..5699f9e5ee7c1 100755 --- a/test/scripts/jenkins_xpack_visual_regression.sh +++ b/test/scripts/jenkins_xpack_visual_regression.sh @@ -1,11 +1,6 @@ #!/usr/bin/env bash -set -e - -if [[ -n "$IS_PIPELINE_JOB" ]] ; then - source src/dev/ci_setup/setup_env.sh -fi - +source test/scripts/jenkins_test_setup.sh source "$KIBANA_DIR/src/dev/ci_setup/setup_percy.sh" if [[ -z "$IS_PIPELINE_JOB" ]] ; then @@ -23,8 +18,6 @@ else export KIBANA_INSTALL_DIR="$destDir" fi -export TEST_BROWSER_HEADLESS=1 - cd "$XPACK_DIR" checks-reporter-with-killswitch "X-Pack visual regression tests" \ diff --git a/test/visual_regression/tests/discover/chart_visualization.js b/test/visual_regression/tests/discover/chart_visualization.js index 5467f2d3eaa14..4e1d9bf643cf6 100644 --- a/test/visual_regression/tests/discover/chart_visualization.js +++ b/test/visual_regression/tests/discover/chart_visualization.js @@ -29,6 +29,7 @@ export default function ({ getService, getPageObjects }) { const visualTesting = getService('visualTesting'); const defaultSettings = { defaultIndex: 'logstash-*', + 'discover:sampleSize': 1 }; describe('discover', function describeIndexTests() { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx index 253580ce5cecd..33b20b0f0f226 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ErrorTabs.tsx @@ -17,7 +17,7 @@ export interface ErrorTab { export const logStacktraceTab: ErrorTab = { key: 'log_stacktrace', label: i18n.translate('xpack.apm.propertiesTable.tabs.logStacktraceLabel', { - defaultMessage: 'Log stacktrace' + defaultMessage: 'Log stack trace' }) }; @@ -26,7 +26,7 @@ export const exceptionStacktraceTab: ErrorTab = { label: i18n.translate( 'xpack.apm.propertiesTable.tabs.exceptionStacktraceLabel', { - defaultMessage: 'Exception stacktrace' + defaultMessage: 'Exception stack trace' } ) }; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx new file mode 100644 index 0000000000000..8ec9c604e7038 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.test.tsx @@ -0,0 +1,41 @@ +/* + * 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 { shallow } from 'enzyme'; +import { ExceptionStacktrace } from './ExceptionStacktrace'; + +describe('ExceptionStacktrace', () => { + describe('render', () => { + it('renders', () => { + const props = { exceptions: [] }; + + expect(() => + shallow() + ).not.toThrowError(); + }); + + describe('with a stack trace', () => { + it('renders the stack trace', () => { + const props = { exceptions: [{}] }; + + expect( + shallow().find('Stacktrace') + ).toHaveLength(1); + }); + }); + + describe('with more than one stack trace', () => { + it('renders a cause stack trace', () => { + const props = { exceptions: [{}, {}] }; + + expect( + shallow().find('CauseStacktrace') + ).toHaveLength(1); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx new file mode 100644 index 0000000000000..13c904a119449 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/ExceptionStacktrace.tsx @@ -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 React from 'react'; +import { EuiTitle } from '@elastic/eui'; +import { idx } from '@kbn/elastic-idx/target'; +import { Exception } from '../../../../../typings/es_schemas/raw/ErrorRaw'; +import { Stacktrace } from '../../../shared/Stacktrace'; +import { CauseStacktrace } from '../../../shared/Stacktrace/CauseStacktrace'; + +interface ExceptionStacktraceProps { + codeLanguage?: string; + exceptions: Exception[]; +} + +export function ExceptionStacktrace({ + codeLanguage, + exceptions +}: ExceptionStacktraceProps) { + const title = idx(exceptions, _ => _[0].message); + + return ( + <> + +

{title}

+
+ {exceptions.map((ex, index) => { + return index === 0 ? ( + + ) : ( + + ); + })} + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap index be9d67f31cb11..39874c11b09bf 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap @@ -46,7 +46,7 @@ exports[`DetailView should render TabContent 1`] = ` currentTab={ Object { "key": "exception_stacktrace", - "label": "Exception stacktrace", + "label": "Exception stack trace", } } error={ @@ -71,7 +71,7 @@ exports[`DetailView should render tabs 1`] = ` key="exception_stacktrace" onClick={[Function]} > - Exception stacktrace + Exception stack trace _.service.language.name); - const excStackframes = idx(error, _ => _.error.exception[0].stacktrace); + const exceptions = idx(error, _ => _.error.exception) || []; const logStackframes = idx(error, _ => _.error.log.stacktrace); switch (currentTab.key) { @@ -198,7 +199,10 @@ export function TabContent({ ); case exceptionStacktraceTab.key: return ( - + ); default: return ; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx index 18e29c92d2e68..b01dc2c5398e2 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/TruncateHeightSection.tsx @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiLink } from '@elastic/eui'; +import { EuiIcon, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { Fragment, useEffect, useRef, useState } from 'react'; import styled from 'styled-components'; import { px, units } from '../../../../../../../style/variables'; -import { Ellipsis } from '../../../../../../shared/Icons'; const ToggleButtonContainer = styled.div` margin-top: ${px(units.half)}; @@ -55,7 +54,13 @@ export const TruncateHeightSection: React.SFC = ({ setIsOpen(!isOpen); }} > - {' '} + {' '} {isOpen ? i18n.translate('xpack.apm.toggleHeight.showLessButtonLabel', { defaultMessage: 'Show fewer lines' diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Icons.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Icons.tsx deleted file mode 100644 index 79b34b2818aac..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/shared/Icons.tsx +++ /dev/null @@ -1,20 +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 { EuiIcon } from '@elastic/eui'; -import React from 'react'; - -export function Ellipsis({ horizontal }: { horizontal: boolean }) { - return ( - - ); -} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/Typeahead/index.js b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/Typeahead/index.js index fc08dee8b93cb..7a7dfe8b50e3e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/Typeahead/index.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/Typeahead/index.js @@ -9,7 +9,6 @@ import PropTypes from 'prop-types'; import Suggestions from './Suggestions'; import ClickOutside from './ClickOutside'; import { EuiFieldSearch, EuiProgress } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; const KEY_CODES = { LEFT: 37, @@ -157,7 +156,7 @@ export class Typeahead extends Component { }; render() { - const { queryExample } = this.props; + const { placeholder } = this.props; return ( { if (node) { this.inputRef = node; @@ -227,7 +217,7 @@ Typeahead.propTypes = { onChange: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, suggestions: PropTypes.array.isRequired, - queryExample: PropTypes.string.isRequired + placeholder: PropTypes.string.isRequired }; Typeahead.defaultProps = { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/get_bool_filter.js b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts similarity index 64% rename from x-pack/legacy/plugins/apm/public/components/shared/KueryBar/get_bool_filter.js rename to x-pack/legacy/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts index 48a94a96acd3c..f4628524cced5 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/get_bool_filter.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ESFilter } from '../../../../typings/elasticsearch'; import { TRANSACTION_TYPE, ERROR_GROUP_ID, @@ -11,27 +12,34 @@ import { TRANSACTION_NAME, SERVICE_NAME } from '../../../../common/elasticsearch_fieldnames'; +import { IUrlParams } from '../../../context/UrlParamsContext/types'; -export function getBoolFilter(urlParams) { - const boolFilter = [ +export function getBoolFilter(urlParams: IUrlParams) { + const { start, end, serviceName, processorEvent } = urlParams; + + if (!start || !end) { + throw new Error('Date range was not defined'); + } + + const boolFilter: ESFilter[] = [ { range: { '@timestamp': { - gte: new Date(urlParams.start).getTime(), - lte: new Date(urlParams.end).getTime(), + gte: new Date(start).getTime(), + lte: new Date(end).getTime(), format: 'epoch_millis' } } } ]; - if (urlParams.serviceName) { + if (serviceName) { boolFilter.push({ - term: { [SERVICE_NAME]: urlParams.serviceName } + term: { [SERVICE_NAME]: serviceName } }); } - switch (urlParams.processorEvent) { + switch (processorEvent) { case 'transaction': boolFilter.push({ term: { [PROCESSOR_EVENT]: 'transaction' } @@ -62,12 +70,19 @@ export function getBoolFilter(urlParams) { } break; + case 'metric': + boolFilter.push({ + term: { [PROCESSOR_EVENT]: 'metric' } + }); + break; + default: boolFilter.push({ bool: { should: [ { term: { [PROCESSOR_EVENT]: 'error' } }, - { term: { [PROCESSOR_EVENT]: 'transaction' } } + { term: { [PROCESSOR_EVENT]: 'transaction' } }, + { term: { [PROCESSOR_EVENT]: 'metric' } } ] } }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index 0e95bb94ea911..7c0b6f24f87a7 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -6,29 +6,20 @@ import React, { useState } from 'react'; import { uniqueId, startsWith } from 'lodash'; -import { EuiCallOut } from '@elastic/eui'; import styled from 'styled-components'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; -import { StaticIndexPattern, getFromSavedObject } from 'ui/index_patterns'; +import { StaticIndexPattern } from 'ui/index_patterns'; import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { i18n } from '@kbn/i18n'; import { fromQuery, toQuery } from '../Links/url_helpers'; -import { KibanaLink } from '../Links/KibanaLink'; // @ts-ignore import { Typeahead } from './Typeahead'; -// @ts-ignore import { getBoolFilter } from './get_bool_filter'; import { useLocation } from '../../../hooks/useLocation'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { history } from '../../../utils/history'; -import { useMatchedRoutes } from '../../../hooks/useMatchedRoutes'; -import { RouteName } from '../../app/Main/route_config/route_names'; -import { useKibanaCore } from '../../../../../observability/public'; -import { ISavedObject } from '../../../services/rest/savedObjects'; import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import { useAPMIndexPattern } from '../../../hooks/useAPMIndexPattern'; +import { useKueryBarIndexPattern } from '../../../hooks/useKueryBarIndexPattern'; const Container = styled.div` margin-bottom: 10px; @@ -50,16 +41,10 @@ function convertKueryToEsQuery( return toElasticsearchQuery(ast, indexPattern); } -function getAPMIndexPatternForKuery( - apmIndexPattern: ISavedObject -): StaticIndexPattern | undefined { - return getFromSavedObject(apmIndexPattern); -} - function getSuggestions( query: string, selectionStart: number, - apmIndexPattern: StaticIndexPattern, + indexPattern: StaticIndexPattern, boolFilter: unknown ) { const autocompleteProvider = getAutocompleteProvider('kuery'); @@ -72,7 +57,7 @@ function getSuggestions( const getAutocompleteSuggestions = autocompleteProvider({ config, - indexPatterns: [apmIndexPattern], + indexPatterns: [indexPattern], boolFilter }); return getAutocompleteSuggestions({ @@ -83,43 +68,28 @@ function getSuggestions( } export function KueryBar() { - const core = useKibanaCore(); const [state, setState] = useState({ suggestions: [], isLoadingSuggestions: false }); const { urlParams } = useUrlParams(); const location = useLocation(); - const matchedRoutes = useMatchedRoutes(); - - const apmIndexPatternTitle = core.injectedMetadata.getInjectedVar( - 'apmIndexPatternTitle' - ); - - const { - apmIndexPattern, - status: apmIndexPatternStatus - } = useAPMIndexPattern(); - - const indexPattern = - apmIndexPatternStatus === FETCH_STATUS.SUCCESS - ? getAPMIndexPatternForKuery(apmIndexPattern) - : null; - - const indexPatternMissing = status === FETCH_STATUS.SUCCESS && !indexPattern; let currentRequestCheck; - const exampleMap: { [key: string]: string } = { - [RouteName.TRANSACTIONS]: 'transaction.duration.us > 300000', - [RouteName.ERRORS]: 'http.response.status_code >= 400', - [RouteName.METRICS]: 'process.pid = "1234"' + const { processorEvent } = urlParams; + + const examples = { + transaction: 'transaction.duration.us > 300000', + error: 'http.response.status_code >= 400', + metric: 'process.pid = "1234"', + defaults: + 'transaction.duration.us > 300000 AND http.response.status_code >= 400' }; - // sets queryExample to the first matched example query, else default example - const queryExample = - matchedRoutes.map(({ name }) => exampleMap[name]).find(Boolean) || - 'transaction.duration.us > 300000 AND http.response.status_code >= 400'; + const example = examples[processorEvent || 'defaults']; + + const { indexPattern } = useKueryBarIndexPattern(processorEvent); async function onChange(inputValue: string, selectionStart: number) { if (indexPattern == null) { @@ -179,42 +149,24 @@ export function KueryBar() { return ( - - {indexPatternMissing && ( - - - {i18n.translate( - 'xpack.apm.kueryBar.setupInstructionsLinkLabel', - { defaultMessage: 'Setup Instructions' } - )} - - ) - }} - /> - + placeholder={i18n.translate('xpack.apm.kueryBar.placeholder', { + defaultMessage: `Search {event, select, + transaction {transactions} + metric {metrics} + error {errors} + other {transactions, errors and metrics} + } (E.g. {queryExample})`, + values: { + queryExample: example, + event: processorEvent } - color="warning" - iconType="alert" - size="s" - /> - )} + })} + /> ); } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx new file mode 100644 index 0000000000000..f36b91a522e92 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.test.tsx @@ -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 React from 'react'; +import { mount, shallow } from 'enzyme'; +import { CauseStacktrace } from './CauseStacktrace'; + +describe('CauseStacktrace', () => { + describe('render', () => { + describe('with no stack trace', () => { + it('renders without the accordion', () => { + const props = { id: 'testId', message: 'testMessage' }; + + expect( + mount().find('CausedBy') + ).toHaveLength(1); + }); + }); + + describe('with no message and a stack trace', () => { + it('says "Caused by …', () => { + const props = { + id: 'testId', + stackframes: [{ filename: 'testFilename', line: { number: 1 } }] + }; + + expect( + mount() + .find('EuiTitle span') + .text() + ).toEqual('…'); + }); + }); + + describe('with a message and a stack trace', () => { + it('renders with the accordion', () => { + const props = { + id: 'testId', + message: 'testMessage', + stackframes: [{ filename: 'testFilename', line: { number: 1 } }] + }; + + expect( + shallow().find('Styled(EuiAccordion)') + ).toHaveLength(1); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx new file mode 100644 index 0000000000000..52f2ba4586718 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/CauseStacktrace.tsx @@ -0,0 +1,79 @@ +/* + * 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 styled from 'styled-components'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { i18n } from '@kbn/i18n'; +import { EuiAccordion, EuiTitle } from '@elastic/eui'; +import { px, unit } from '../../../style/variables'; +import { Stacktrace } from '.'; +import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; + +// @ts-ignore Styled Components has trouble inferring the types of the default props here. +const Accordion = styled(EuiAccordion)` + border-top: ${theme.euiBorderThin}; +`; + +const CausedByContainer = styled('h5')` + padding: ${theme.spacerSizes.s} 0; +`; + +const CausedByHeading = styled('span')` + color: ${theme.textColors.subdued}; + display: block; + font-size: ${theme.euiFontSizeXS}; + font-weight: ${theme.euiFontWeightBold}; + text-transform: uppercase; +`; + +const FramesContainer = styled('div')` + padding-left: ${px(unit)}; +`; + +function CausedBy({ message }: { message: string }) { + return ( + + + {i18n.translate( + 'xpack.apm.stacktraceTab.causedByFramesToogleButtonLabel', + { + defaultMessage: 'Caused By' + } + )} + + + {message} + + + ); +} + +interface CauseStacktraceProps { + codeLanguage?: string; + id: string; + message?: string; + stackframes?: IStackframe[]; +} + +export function CauseStacktrace({ + codeLanguage, + id, + message = '…', + stackframes = [] +}: CauseStacktraceProps) { + if (stackframes.length === 0) { + return ; + } + + return ( + } id={id}> + + + + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx index c5d610d0b8e1b..9685ba920a73c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Context.tsx @@ -32,7 +32,7 @@ registerLanguage('ruby', ruby); const ContextContainer = styled.div` position: relative; - border-radius: 0 0 ${borderRadius} ${borderRadius}; + border-radius: ${borderRadius}; `; const LINE_HEIGHT = units.eighth * 9; @@ -49,7 +49,7 @@ const LineNumberContainer = styled.div<{ isLibraryFrame: boolean }>` position: absolute; top: 0; left: 0; - border-radius: 0 0 0 ${borderRadius}; + border-radius: ${borderRadius}; background: ${props => props.isLibraryFrame ? theme.euiColorEmptyShade diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx index c9f7158f464a4..c9f7057d2fb86 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx @@ -12,16 +12,17 @@ import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackfram import { fontFamilyCode, fontSize, px, units } from '../../../style/variables'; const FileDetails = styled.div` - color: ${theme.euiColorMediumShade}; - padding: ${px(units.half)}; + color: ${theme.euiColorDarkShade}; + padding: ${px(units.half)} 0; font-family: ${fontFamilyCode}; font-size: ${fontSize}; `; + const LibraryFrameFileDetail = styled.span` color: ${theme.euiColorDarkShade}; `; + const AppFrameFileDetail = styled.span` - font-weight: bold; color: ${theme.euiColorFullShade}; `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStackFrames.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStackFrames.tsx deleted file mode 100644 index 9f8229e024c37..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStackFrames.tsx +++ /dev/null @@ -1,79 +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 { EuiLink, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import React, { Fragment } from 'react'; -import styled from 'styled-components'; -import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; -import { Ellipsis } from '../../shared/Icons'; -import { Stackframe } from './Stackframe'; - -const LibraryFrameToggle = styled.div` - user-select: none; -`; - -interface Props { - stackframes: IStackframe[]; - codeLanguage?: string; - initialVisiblity: boolean; -} - -interface State { - isVisible: boolean; -} - -export class LibraryStackFrames extends React.Component { - public state = { - isVisible: this.props.initialVisiblity - }; - - public onClick = () => { - this.setState(({ isVisible }) => ({ isVisible: !isVisible })); - }; - - public render() { - const { stackframes, codeLanguage } = this.props; - const { isVisible } = this.state; - if (stackframes.length === 0) { - return null; - } - - return ( -
- - - {' '} - {i18n.translate( - 'xpack.apm.stacktraceTab.libraryFramesToogleButtonLabel', - { - defaultMessage: - '{count, plural, one {# library frame} other {# library frames}}', - values: { count: stackframes.length } - } - )} - - - -
- {isVisible && ( - - - {stackframes.map((stackframe, i) => ( - - ))} - - )} -
-
- ); - } -} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx new file mode 100644 index 0000000000000..94751f9d0461d --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.test.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; +import { LibraryStacktrace } from './LibraryStacktrace'; + +describe('LibraryStacktrace', () => { + describe('render', () => { + describe('with no stack frames', () => { + it('renders null', () => { + const props = { id: 'testId', stackframes: [] }; + + expect(shallow().html()).toBeNull(); + }); + }); + + describe('with stack frames', () => { + it('renders an accordion', () => { + const props = { + id: 'testId', + stackframes: [{ filename: 'testFilename', line: { number: 1 } }] + }; + + expect( + shallow().find('EuiAccordion') + ).toHaveLength(1); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx new file mode 100644 index 0000000000000..f62ba95407893 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/LibraryStacktrace.tsx @@ -0,0 +1,55 @@ +/* + * 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 { EuiAccordion } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import styled from 'styled-components'; +import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; +import { Stackframe } from './Stackframe'; +import { px, unit } from '../../../style/variables'; + +const FramesContainer = styled('div')` + padding-left: ${px(unit)}; +`; + +interface Props { + codeLanguage?: string; + stackframes: IStackframe[]; + id: string; +} + +export function LibraryStacktrace({ codeLanguage, id, stackframes }: Props) { + if (stackframes.length === 0) { + return null; + } + + return ( + + + {stackframes.map((stackframe, i) => ( + + ))} + + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx index cd6c7ce9224a5..a307cc56cc71a 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Stackframe.tsx @@ -7,6 +7,7 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import React from 'react'; import styled from 'styled-components'; +import { EuiAccordion } from '@elastic/eui'; import { IStackframe, IStackframeWithLineContext @@ -16,16 +17,11 @@ import { fontFamilyCode, fontSize } from '../../../style/variables'; -import { FrameHeading } from '../Stacktrace/FrameHeading'; +import { FrameHeading } from './FrameHeading'; import { Context } from './Context'; import { Variables } from './Variables'; -const CodeHeader = styled.div` - border-bottom: 1px solid ${theme.euiColorLightShade}; - border-radius: ${borderRadius} ${borderRadius} 0 0; -`; - -const Container = styled.div<{ isLibraryFrame: boolean }>` +const ContextContainer = styled.div<{ isLibraryFrame: boolean }>` position: relative; font-family: ${fontFamilyCode}; font-size: ${fontSize}; @@ -40,12 +36,16 @@ const Container = styled.div<{ isLibraryFrame: boolean }>` interface Props { stackframe: IStackframe; codeLanguage?: string; + id: string; + initialIsOpen?: boolean; isLibraryFrame?: boolean; } export function Stackframe({ stackframe, codeLanguage, + id, + initialIsOpen = false, isLibraryFrame = false }: Props) { if (!hasLineContext(stackframe)) { @@ -55,19 +55,22 @@ export function Stackframe({ } return ( - - + - - - - + } + id={id} + initialIsOpen={initialIsOpen} + > + + + - + ); } diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx index 534485424a496..6d1b10c90a999 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx @@ -16,7 +16,6 @@ import { flattenObject } from '../../../utils/flattenObject'; const VariablesContainer = styled.div` background: ${theme.euiColorEmptyShade}; - border-top: 1px solid ${theme.euiColorLightShade}; border-radius: 0 0 ${borderRadius} ${borderRadius}; padding: ${px(units.half)} ${px(unit)}; `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx index b0052e8c17b64..5f6519b7c7b96 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/Stackframe.test.tsx @@ -16,7 +16,7 @@ describe('Stackframe', () => { let wrapper: ReactWrapper; beforeEach(() => { const stackframe = stacktracesMock[0]; - wrapper = mount(); + wrapper = mount(); }); it('should render correctly', () => { @@ -38,7 +38,7 @@ describe('Stackframe', () => { let wrapper: ReactWrapper; beforeEach(() => { const stackframe = { line: {} } as IStackframe; - wrapper = mount(); + wrapper = mount(); }); it('should render only FrameHeading', () => { @@ -55,7 +55,7 @@ describe('Stackframe', () => { it('should respect isLibraryFrame', () => { const stackframe = { line: {} } as IStackframe; const wrapper = shallow( - + ); expect(wrapper.find('FrameHeading').prop('isLibraryFrame')).toBe(true); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap index 1b465db1c6263..55cec7389dc5c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/__test__/__snapshots__/Stackframe.test.tsx.snap @@ -1,24 +1,23 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Stackframe when stackframe has source lines should render correctly 1`] = ` -.c2 { - color: #98a2b3; - padding: 8px; +.c0 { + color: #69707d; + padding: 8px 0; font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; font-size: 14px; } -.c3 { - font-weight: bold; +.c1 { color: #000000; } -.c4 { +.c3 { position: relative; - border-radius: 0 0 4px 4px; + border-radius: 4px; } -.c5 { +.c4 { position: absolute; width: 100%; height: 18px; @@ -27,15 +26,15 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] background-color: #fef6e5; } -.c6 { +.c5 { position: absolute; top: 0; left: 0; - border-radius: 0 0 0 4px; + border-radius: 4px; background: #f5f7fa; } -.c7 { +.c6 { position: relative; min-width: 42px; padding-left: 8px; @@ -46,11 +45,11 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] border-right: 1px solid #d3dae6; } -.c7:last-of-type { +.c6:last-of-type { border-radius: 0 0 0 4px; } -.c8 { +.c7 { position: relative; min-width: 42px; padding-left: 8px; @@ -62,22 +61,22 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] background-color: #fef6e5; } -.c8:last-of-type { +.c7:last-of-type { border-radius: 0 0 0 4px; } -.c9 { +.c8 { overflow: auto; margin: 0 0 0 42px; padding: 0; background-color: #ffffff; } -.c9:last-of-type { +.c8:last-of-type { border-radius: 0 0 4px 0; } -.c10 { +.c9 { margin: 0; color: inherit; background: inherit; @@ -88,7 +87,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] line-height: 18px; } -.c11 { +.c10 { position: relative; padding: 0; margin: 0; @@ -96,12 +95,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] z-index: 2; } -.c1 { - border-bottom: 1px solid #d3dae6; - border-radius: 4px 4px 0 0; -} - -.c0 { +.c2 { position: relative; font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; font-size: 14px; @@ -111,6 +105,7 @@ exports[`Stackframe when stackframe has source lines should render correctly 1`] } - -
- -
- ", - "library_frame": false, - "line": Object { - "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", - "number": 307, - }, - } - } - > - -
- - - server/routes.js - - - in - - - - <anonymous> - - - at - - - line - 307 - - -
-
-
-
-
- + } + id="test" + initialIsOpen={false} + paddingSize="none" + > +
+
- -
+ - -
- - + + + + + + ", + "library_frame": false, + "line": Object { + "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", + "number": 307, + }, + } + } > -
- -
- 305 - . -
-
- -
- 306 - . -
-
- -
- 307 - . -
-
- -
- 308 - . -
-
- +
-
- 309 - . -
- -
-
- -
- - -
+                    
-                      
-                        
-                              })
-                        
-                      
-                    
-
-
- - -
+                  
+                   in
+                   
+                  
+                    
-                      
-                        
-                          
-
-                        
-                      
-                    
-
-
- - -
+                  
+                   at 
+                  
+                    
-                      
-                        
-                              client.query(
-                          
-                            'SELECT id FROM customers WHERE id=$1'
-                          
-                          , [req.body.customer_id], next())
-                        
-                      
-                    
-
-
- + +
+
+ + + +
+
+ +
+
+ +
- ", + "library_frame": false, + "line": Object { + "context": " client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())", + "number": 307, + }, } } > -
-                      
-                        
+                      
+ +
+ + - req.body.lines.forEach( - - +
+ 305 + . +
+
+ +
+ 306 + . +
+
+ +
+ 307 + . +
+
+ +
+ 308 + . +
+
+ +
+ 309 + . +
+
+
+
+ +
+ - function - - ( - +
+                                  
+                                    
+                                          })
+                                    
+                                  
+                                
+ +
+ - line - - ) - - { -
- -
-
- - - -
-                      
-                        
-                                client.query(
-                          
+                                
+                                  
+                                    
+                                      
+
+                                    
+                                  
+                                
+ + + - 'SELECT id FROM products WHERE id=$1' -
- , [line.id], next()) -
-
-
-
-
-
-
+ key=" client.query('SELECT id FROM customers WHERE id=$1', [req.body.customer_id], next())2" + language="javascript" + style={ + Object { + "hljs": Object { + "background": "#fff", + "color": "black", + "display": "block", + "overflowX": "auto", + "padding": "0.5em", + }, + "hljs-addition": Object { + "backgroundColor": "#baeeba", + }, + "hljs-attr": Object { + "color": "#5c2699", + }, + "hljs-attribute": Object { + "color": "#000", + }, + "hljs-built_in": Object { + "color": "#5c2699", + }, + "hljs-builtin-name": Object { + "color": "#5c2699", + }, + "hljs-bullet": Object { + "color": "#1c00cf", + }, + "hljs-class .hljs-title": Object { + "color": "#5c2699", + }, + "hljs-comment": Object { + "color": "#006a00", + }, + "hljs-deletion": Object { + "backgroundColor": "#ffc8bd", + }, + "hljs-doctag": Object { + "fontWeight": "bold", + }, + "hljs-emphasis": Object { + "fontStyle": "italic", + }, + "hljs-formula": Object { + "backgroundColor": "#eee", + "fontStyle": "italic", + }, + "hljs-keyword": Object { + "color": "#aa0d91", + }, + "hljs-link": Object { + "color": "#080", + }, + "hljs-literal": Object { + "color": "#aa0d91", + }, + "hljs-meta": Object { + "color": "#1c00cf", + }, + "hljs-name": Object { + "color": "#008", + }, + "hljs-number": Object { + "color": "#1c00cf", + }, + "hljs-params": Object { + "color": "#5c2699", + }, + "hljs-quote": Object { + "color": "#006a00", + }, + "hljs-regexp": Object { + "color": "#080", + }, + "hljs-section": Object { + "color": "#5c2699", + }, + "hljs-selector-class": Object { + "color": "#9b703f", + }, + "hljs-selector-id": Object { + "color": "#9b703f", + }, + "hljs-selector-tag": Object { + "color": "#aa0d91", + }, + "hljs-string": Object { + "color": "#c41a16", + }, + "hljs-strong": Object { + "fontWeight": "bold", + }, + "hljs-subst": Object { + "color": "#000", + }, + "hljs-symbol": Object { + "color": "#1c00cf", + }, + "hljs-tag": Object { + "color": "#1c00cf", + }, + "hljs-template-variable": Object { + "color": "#660", + }, + "hljs-title": Object { + "color": "#1c00cf", + }, + "hljs-type": Object { + "color": "#5c2699", + }, + "hljs-variable": Object { + "color": "#660", + }, + } + } + > + +
+                                  
+                                    
+                                          client.query(
+                                      
+                                        'SELECT id FROM customers WHERE id=$1'
+                                      
+                                      , [req.body.customer_id], next())
+                                    
+                                  
+                                
+
+ + + +
+                                  
+                                    
+                                          req.body.lines.forEach(
+                                      
+                                        
+                                          function
+                                        
+                                         (
+                                        
+                                          line
+                                        
+                                        ) 
+                                      
+                                      {
+                                    
+                                  
+                                
+
+
+ + +
+                                  
+                                    
+                                            client.query(
+                                      
+                                        'SELECT id FROM products WHERE id=$1'
+                                      
+                                      , [line.id], next())
+                                    
+                                  
+                                
+
+
+
+ +
+ + +
+
+ +
-
- - + +
- + `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx index ffa740792cede..ca14be237d22b 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/index.tsx @@ -10,7 +10,7 @@ import { isEmpty, last } from 'lodash'; import React, { Fragment } from 'react'; import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; import { EmptyMessage } from '../../shared/EmptyMessage'; -import { LibraryStackFrames } from './LibraryStackFrames'; +import { LibraryStacktrace } from './LibraryStacktrace'; import { Stackframe } from './Stackframe'; interface Props { @@ -25,7 +25,7 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { heading={i18n.translate( 'xpack.apm.stacktraceTab.noStacktraceAvailableLabel', { - defaultMessage: 'No stacktrace available.' + defaultMessage: 'No stack trace available.' } )} hideSubheading @@ -34,16 +34,17 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { } const groups = getGroupedStackframes(stackframes); + return ( {groups.map((group, i) => { // library frame - if (group.isLibraryFrame) { + if (group.isLibraryFrame && groups.length > 1) { return ( - @@ -56,7 +57,12 @@ export function Stacktrace({ stackframes = [], codeLanguage }: Props) { return group.stackframes.map((stackframe, idx) => ( {idx > 0 && } - + 1} + stackframe={stackframe} + /> )); })} diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts index 29a27f034be8a..75b5e0f2a05c8 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/helpers.ts @@ -8,6 +8,13 @@ import { compact, pick } from 'lodash'; import datemath from '@elastic/datemath'; import { IUrlParams } from './types'; +interface PathParams { + processorEvent?: 'error' | 'metric' | 'transaction'; + serviceName?: string; + errorGroupId?: string; + serviceNodeName?: string; +} + export function getParsedDate(rawDate?: string, opts = {}) { if (rawDate) { const parsed = datemath.parse(rawDate, opts); @@ -56,7 +63,7 @@ export function removeUndefinedProps(obj: T): Partial { return pick(obj, value => value !== undefined); } -export function getPathParams(pathname: string = '') { +export function getPathParams(pathname: string = ''): PathParams { const paths = getPathAsArray(pathname); const pageName = paths[0]; @@ -92,17 +99,15 @@ export function getPathParams(pathname: string = '') { }; case 'nodes': return { + processorEvent: 'metric', serviceName }; case 'service-map': return { - processorEvent: 'service-map', serviceName }; default: - return { - processorEvent: 'transaction' - }; + return {}; } case 'traces': diff --git a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts index 5578acaaec34b..fc5f5dfb6e301 100644 --- a/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts +++ b/x-pack/legacy/plugins/apm/public/context/UrlParamsContext/types.ts @@ -30,4 +30,5 @@ export type IUrlParams = { pageSize?: number; serviceNodeName?: string; searchTerm?: string; + processorEvent?: 'transaction' | 'error' | 'metric'; } & Partial>; diff --git a/x-pack/legacy/plugins/apm/public/hooks/useKueryBarIndexPattern.ts b/x-pack/legacy/plugins/apm/public/hooks/useKueryBarIndexPattern.ts new file mode 100644 index 0000000000000..5fb9405559d56 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/hooks/useKueryBarIndexPattern.ts @@ -0,0 +1,31 @@ +/* + * 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 { useFetcher } from './useFetcher'; + +export function useKueryBarIndexPattern( + processorEvent: 'transaction' | 'metric' | 'error' | undefined +) { + const { data: indexPattern, status } = useFetcher( + callApmApi => { + return callApmApi({ + pathname: '/api/apm/kuery_bar_index_pattern', + forceCache: true, + params: { + query: { + processorEvent + } + } + }); + }, + [processorEvent] + ); + + return { + indexPattern, + status + }; +} diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/getKueryBarIndexPattern.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/getKueryBarIndexPattern.ts new file mode 100644 index 0000000000000..33d404009ae20 --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/getKueryBarIndexPattern.ts @@ -0,0 +1,53 @@ +/* + * 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 { Legacy } from 'kibana'; +import { StaticIndexPattern } from 'ui/index_patterns'; +import { APICaller } from 'src/core/server'; +import { IndexPatternsService } from '../../../../../../../src/legacy/server/index_patterns/service'; +import { Setup } from '../helpers/setup_request'; + +export const getKueryBarIndexPattern = async ({ + request, + processorEvent, + setup +}: { + request: Legacy.Request; + processorEvent?: 'transaction' | 'error' | 'metric'; + setup: Setup; +}) => { + const { indices } = setup; + + const indexPatternsService = new IndexPatternsService( + (...rest: Parameters) => + request.server.plugins.elasticsearch + .getCluster('data') + .callWithRequest(request, ...rest) + ); + + const indexNames = processorEvent + ? [processorEvent] + : ['transaction' as const, 'metric' as const, 'error' as const]; + + const indicesMap = { + transaction: indices['apm_oss.transactionIndices'], + metric: indices['apm_oss.metricsIndices'], + error: indices['apm_oss.errorIndices'] + }; + + const configuredIndices = indexNames.map(name => indicesMap[name]); + + const fields = await indexPatternsService.getFieldsForWildcard({ + pattern: configuredIndices + }); + + const pattern: StaticIndexPattern = { + fields, + title: configuredIndices.join(',') + }; + + return pattern; +}; diff --git a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts index 516f1994c2fb7..7d22d9488718f 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { indexPatternRoute } from './index_pattern'; +import { indexPatternRoute, kueryBarIndexPatternRoute } from './index_pattern'; import { errorDistributionRoute, errorGroupsRoute, @@ -58,6 +58,7 @@ const createApmApi = () => { const api = createApi() // index pattern .add(indexPatternRoute) + .add(kueryBarIndexPatternRoute) // Errors .add(errorDistributionRoute) diff --git a/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts b/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts index e838fb1a4b526..100df4dc238fe 100644 --- a/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts +++ b/x-pack/legacy/plugins/apm/server/routes/index_pattern.ts @@ -3,8 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import * as t from 'io-ts'; import { getAPMIndexPattern } from '../lib/index_pattern'; import { createRoute } from './create_route'; +import { getKueryBarIndexPattern } from '../lib/index_pattern/getKueryBarIndexPattern'; +import { setupRequest } from '../lib/helpers/setup_request'; export const indexPatternRoute = createRoute(core => ({ path: '/api/apm/index_pattern', @@ -13,3 +16,23 @@ export const indexPatternRoute = createRoute(core => ({ return await getAPMIndexPattern(server); } })); + +export const kueryBarIndexPatternRoute = createRoute(core => ({ + path: '/api/apm/kuery_bar_index_pattern', + params: { + query: t.partial({ + processorEvent: t.union([ + t.literal('transaction'), + t.literal('metric'), + t.literal('error') + ]) + }) + }, + handler: async (request, { query }) => { + const { processorEvent } = query; + + const setup = await setupRequest(request); + + return getKueryBarIndexPattern({ request, processorEvent, setup }); + } +})); diff --git a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts index 545bcc86b2f95..2f6e857e82470 100644 --- a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts +++ b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/ErrorRaw.ts @@ -21,7 +21,7 @@ interface Processor { event: 'error'; } -interface Exception { +export interface Exception { message?: string; // either message or type are given type?: string; module?: string; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts index cad46e81ffc0c..063e69d1d2141 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionType } from 'src/plugins/expressions/common'; +import { ExpressionType } from 'src/plugins/expressions/public'; import { EmbeddableInput } from 'src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { EmbeddableTypes } from './embeddable_types'; diff --git a/x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js b/x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js index 760298c8a2dff..f867e4dc77a11 100644 --- a/x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js +++ b/x-pack/legacy/plugins/canvas/scripts/shareable_runtime.js @@ -91,10 +91,20 @@ run( clean(); log.info('Building Canvas Shareable Workpad Runtime...'); - execa.sync('yarn', ['webpack', '--config', webpackConfig, '--hide-modules', '--progress'], { - ...options, - env, - }); + execa.sync( + 'yarn', + [ + 'webpack', + '--config', + webpackConfig, + '--hide-modules', + ...(process.stdout.isTTY ? ['--progress'] : []), + ], + { + ...options, + env, + } + ); log.success('...runtime built!'); }, { diff --git a/x-pack/legacy/plugins/canvas/types/elements.ts b/x-pack/legacy/plugins/canvas/types/elements.ts index b3af9e5eee70d..c70f2af1002aa 100644 --- a/x-pack/legacy/plugins/canvas/types/elements.ts +++ b/x-pack/legacy/plugins/canvas/types/elements.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExpressionAST } from 'src/plugins/expressions/common/expressions'; +import { ExpressionAST } from 'src/plugins/expressions/public'; import { CanvasElement } from '.'; export interface ElementSpec { diff --git a/x-pack/legacy/plugins/canvas/types/index.ts b/x-pack/legacy/plugins/canvas/types/index.ts index 8c857d8d5f3a9..54b73b2d2f362 100644 --- a/x-pack/legacy/plugins/canvas/types/index.ts +++ b/x-pack/legacy/plugins/canvas/types/index.ts @@ -10,7 +10,7 @@ export { BackgroundRepeat, BackgroundSize, } from '../../../../../src/legacy/core_plugins/interpreter/public/types/style'; -export * from '../../../../../src/plugins/expressions/common'; +export * from '../../../../../src/plugins/expressions/public'; export * from './assets'; export * from './canvas'; export * from './elements'; diff --git a/x-pack/legacy/plugins/canvas/types/state.ts b/x-pack/legacy/plugins/canvas/types/state.ts index 2deab0a6fc8a4..494965d060788 100644 --- a/x-pack/legacy/plugins/canvas/types/state.ts +++ b/x-pack/legacy/plugins/canvas/types/state.ts @@ -13,7 +13,7 @@ import { Render, Style, Range, -} from 'src/plugins/expressions/common/expressions/expression_types'; +} from 'src/plugins/expressions/public'; import { CanvasFunction } from './functions'; import { AssetType } from './assets'; import { CanvasWorkpad } from './canvas'; diff --git a/x-pack/legacy/plugins/infra/public/components/eui/toolbar/toolbar.tsx b/x-pack/legacy/plugins/infra/public/components/eui/toolbar/toolbar.tsx index b65f3043c2144..6f1cfc4078ae2 100644 --- a/x-pack/legacy/plugins/infra/public/components/eui/toolbar/toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/components/eui/toolbar/toolbar.tsx @@ -17,5 +17,4 @@ export const Toolbar = euiStyled(EuiPanel).attrs({ border-left: none; border-radius: 0; padding: ${props => props.theme.eui.euiSizeS} ${props => props.theme.eui.euiSizeL}; - z-index: ${props => props.theme.eui.euiZLevel1}; `; diff --git a/x-pack/legacy/plugins/infra/public/components/navigation/routed_tabs.tsx b/x-pack/legacy/plugins/infra/public/components/navigation/routed_tabs.tsx index 437a0eb513da0..0b1d34870a72d 100644 --- a/x-pack/legacy/plugins/infra/public/components/navigation/routed_tabs.tsx +++ b/x-pack/legacy/plugins/infra/public/components/navigation/routed_tabs.tsx @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiTab, EuiTabs, EuiLink } from '@elastic/eui'; +import { EuiBetaBadge, EuiLink, EuiTab, EuiTabs } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { Route } from 'react-router-dom'; + import euiStyled from '../../../../../common/eui_styled_components'; interface TabConfiguration { @@ -54,6 +56,24 @@ export class RoutedTabs extends React.Component { } } +const tabBetaBadgeLabel = i18n.translate('xpack.infra.common.tabBetaBadgeLabel', { + defaultMessage: 'Beta', +}); + +const tabBetaBadgeTooltipContent = i18n.translate('xpack.infra.common.tabBetaBadgeTooltipContent', { + defaultMessage: + 'This feature is under active development. Extra functionality is coming, and some functionality may change.', +}); + +export const TabBetaBadge = euiStyled(EuiBetaBadge).attrs({ + 'aria-label': tabBetaBadgeLabel, + label: tabBetaBadgeLabel, + tooltipContent: tabBetaBadgeTooltipContent, +})` + margin-left: 4px; + vertical-align: baseline; +`; + const TabContainer = euiStyled.div` .euiLink { color: inherit !important; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx index 8bd9f1074ac54..81a80fb565a4b 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_results.tsx @@ -8,6 +8,23 @@ import createContainer from 'constate'; import { useMemo, useEffect } from 'react'; import { useLogEntryRate } from './log_entry_rate'; +import { GetLogEntryRateSuccessResponsePayload } from '../../../../common/http_api/log_analysis'; + +type PartitionBucket = { + startTime: number; +} & GetLogEntryRateSuccessResponsePayload['data']['histogramBuckets'][0]['partitions'][0]; + +type PartitionRecord = Record< + string, + { buckets: PartitionBucket[]; topAnomalyScore: number; totalNumberOfLogEntries: number } +>; + +export interface LogRateResults { + bucketDuration: number; + totalNumberOfLogEntries: number; + histogramBuckets: GetLogEntryRateSuccessResponsePayload['data']['histogramBuckets']; + partitionBuckets: PartitionRecord; +} export const useLogAnalysisResults = ({ sourceId, @@ -35,10 +52,66 @@ export const useLogAnalysisResults = ({ getLogEntryRate(); }, [sourceId, startTime, endTime, bucketDuration, lastRequestTime]); + const logRateResults: LogRateResults | null = useMemo(() => { + if (logEntryRate) { + return { + bucketDuration: logEntryRate.bucketDuration, + totalNumberOfLogEntries: logEntryRate.totalNumberOfLogEntries, + histogramBuckets: logEntryRate.histogramBuckets, + partitionBuckets: formatLogEntryRateResultsByPartition(logEntryRate), + }; + } else { + return null; + } + }, [logEntryRate]); + return { isLoading, - logEntryRate, + logRateResults, }; }; export const LogAnalysisResults = createContainer(useLogAnalysisResults); + +const formatLogEntryRateResultsByPartition = ( + results: GetLogEntryRateSuccessResponsePayload['data'] +): PartitionRecord => { + const partitionedBuckets = results.histogramBuckets.reduce< + Record + >((partitionResults, bucket) => { + return bucket.partitions.reduce>( + (_partitionResults, partition) => { + return { + ..._partitionResults, + [partition.partitionId]: { + buckets: _partitionResults[partition.partitionId] + ? [ + ..._partitionResults[partition.partitionId].buckets, + { startTime: bucket.startTime, ...partition }, + ] + : [{ startTime: bucket.startTime, ...partition }], + }, + }; + }, + partitionResults + ); + }, {}); + + const resultsByPartition: PartitionRecord = {}; + + Object.entries(partitionedBuckets).map(([key, value]) => { + const anomalyScores = value.buckets.reduce((scores: number[], bucket) => { + return [...scores, bucket.maximumAnomalyScore]; + }, []); + const totalNumberOfLogEntries = value.buckets.reduce((total, bucket) => { + return (total += bucket.numberOfLogEntries); + }, 0); + resultsByPartition[key] = { + topAnomalyScore: Math.max(...anomalyScores), + totalNumberOfLogEntries, + buckets: value.buckets, + }; + }); + + return resultsByPartition; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index e740689da50f6..7fa9ff3c93db7 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -82,15 +82,15 @@ export const AnalysisResultsContent = ({ return roundedResult < bucketSpan ? bucketSpan : roundedResult; }, [queryTimeRange.value.startTime, queryTimeRange.value.endTime]); - const { isLoading, logEntryRate } = useLogAnalysisResults({ + const { isLoading, logRateResults } = useLogAnalysisResults({ sourceId, startTime: queryTimeRange.value.startTime, endTime: queryTimeRange.value.endTime, bucketDuration, lastRequestTime: queryTimeRange.lastChangedTime, }); - const hasResults = useMemo(() => logEntryRate && logEntryRate.histogramBuckets.length > 0, [ - logEntryRate, + const hasResults = useMemo(() => logRateResults && logRateResults.histogramBuckets.length > 0, [ + logRateResults, ]); const handleQueryTimeRangeChange = useCallback( @@ -168,7 +168,7 @@ export const AnalysisResultsContent = ({ - {logEntryRate ? ( + {logRateResults ? ( - {numeral(logEntryRate.totalNumberOfLogEntries).format('0.00a')} + {numeral(logRateResults.totalNumberOfLogEntries).format('0.00a')} ), @@ -210,7 +210,7 @@ export const AnalysisResultsContent = ({ {isFirstUse && !hasResults ? : null} @@ -223,7 +223,7 @@ export const AnalysisResultsContent = ({ jobStatus={jobStatus['log-entry-rate']} viewSetupForReconfiguration={viewSetupForReconfiguration} viewSetupForUpdate={viewSetupForUpdate} - results={logEntryRate} + results={logRateResults} setTimeRange={handleChartTimeRangeChange} setupStatus={setupStatus} timeRange={queryTimeRange.value} diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx index 191e3cb7d7bfd..317bd6ec5e925 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx @@ -25,7 +25,7 @@ export const AnalyzeInMlButton: React.FunctionComponent<{ defaultMessage="Analyze in ML" /> ); - return partition ? ( + return typeof partition === 'string' ? ( void; timeRange: TimeRange; jobId: string; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx index c340de4ad3848..26f44519312e5 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx @@ -18,7 +18,7 @@ import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import euiStyled from '../../../../../../../../common/eui_styled_components'; -import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/http_api'; +import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; import { JobStatus, SetupStatus } from '../../../../../../common/log_analysis'; import { @@ -36,7 +36,7 @@ import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay export const AnomaliesResults: React.FunctionComponent<{ isLoading: boolean; jobStatus: JobStatus; - results: GetLogEntryRateSuccessResponsePayload['data'] | null; + results: LogRateResults | null; setTimeRange: (timeRange: TimeRange) => void; setupStatus: SetupStatus; timeRange: TimeRange; @@ -195,7 +195,7 @@ const title = i18n.translate('xpack.infra.logs.analysis.anomaliesSectionTitle', }); interface ParsedAnnotationDetails { - anomalyScoresByPartition: Array<{ partitionId: string; maximumAnomalyScore: number }>; + anomalyScoresByPartition: Array<{ partitionName: string; maximumAnomalyScore: number }>; } const overallAnomalyScoreLabel = i18n.translate( @@ -213,11 +213,11 @@ const AnnotationTooltip: React.FunctionComponent<{ details: string }> = ({ detai {overallAnomalyScoreLabel}
    - {parsedDetails.anomalyScoresByPartition.map(({ partitionId, maximumAnomalyScore }) => { + {parsedDetails.anomalyScoresByPartition.map(({ partitionName, maximumAnomalyScore }) => { return ( -
  • +
  • - {`${partitionId}: `} + {`${partitionName}: `} {maximumAnomalyScore}
  • diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx index 88ffcae6e9dea..ebf31d8320df5 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx @@ -9,9 +9,9 @@ import { EuiBasicTable, EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; -import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/http_api/log_analysis/results/log_entry_rate'; +import { LogRateResults } from '../../../../../containers/logs/log_analysis/log_analysis_results'; import { AnomaliesTableExpandedRow } from './expanded_row'; -import { getTopAnomalyScoresByPartition, formatAnomalyScore } from '../helpers/data_formatters'; +import { formatAnomalyScore, getFriendlyNameForPartitionId } from '../helpers/data_formatters'; interface TableItem { id: string; @@ -49,17 +49,20 @@ const maxAnomalyScoreColumnName = i18n.translate( ); export const AnomaliesTable: React.FunctionComponent<{ - results: GetLogEntryRateSuccessResponsePayload['data']; + results: LogRateResults; setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; jobId: string; }> = ({ results, timeRange, setTimeRange, jobId }) => { const tableItems: TableItem[] = useMemo(() => { - return Object.entries(getTopAnomalyScoresByPartition(results)).map(([key, value]) => { + return Object.entries(results.partitionBuckets).map(([key, value]) => { return { - id: key || 'unknown', // Note: EUI's table expanded rows won't work with a key of '' in itemIdToExpandedRowMap - partition: key || 'unknown', - topAnomalyScore: formatAnomalyScore(value), + // Note: EUI's table expanded rows won't work with a key of '' in itemIdToExpandedRowMap, so we have to use the friendly name here + id: getFriendlyNameForPartitionId(key), + // The real ID + partitionId: key, + partition: getFriendlyNameForPartitionId(key), + topAnomalyScore: formatAnomalyScore(value.topAnomalyScore), }; }); }, [results]); @@ -108,7 +111,7 @@ export const AnomaliesTable: React.FunctionComponent<{ ...itemIdToExpandedRowMap, [item.id]: ( ; @@ -17,15 +17,13 @@ const ML_SEVERITY_SCORES: MLSeverityScores = { critical: 75, }; -export const getLogEntryRatePartitionedSeries = ( - results: GetLogEntryRateSuccessResponsePayload['data'] -) => { +export const getLogEntryRatePartitionedSeries = (results: LogRateResults) => { return results.histogramBuckets.reduce>( (buckets, bucket) => { return [ ...buckets, ...bucket.partitions.map(partition => ({ - group: partition.partitionId === '' ? 'unknown' : partition.partitionId, + group: getFriendlyNameForPartitionId(partition.partitionId), time: bucket.startTime, value: partition.averageActualLogEntryRate, })), @@ -35,9 +33,7 @@ export const getLogEntryRatePartitionedSeries = ( ); }; -export const getLogEntryRateCombinedSeries = ( - results: GetLogEntryRateSuccessResponsePayload['data'] -) => { +export const getLogEntryRateCombinedSeries = (results: LogRateResults) => { return results.histogramBuckets.reduce>( (buckets, bucket) => { return [ @@ -54,69 +50,27 @@ export const getLogEntryRateCombinedSeries = ( ); }; -export const getLogEntryRateSeriesForPartition = ( - results: GetLogEntryRateSuccessResponsePayload['data'], - partitionId: string -) => { - return results.histogramBuckets.reduce>( - (buckets, bucket) => { - const partitionResults = bucket.partitions.find(partition => { - return ( - partition.partitionId === partitionId || - (partition.partitionId === '' && partitionId === 'unknown') - ); - }); - if (!partitionResults) { - return buckets; - } - return [ - ...buckets, - { - time: bucket.startTime, - value: partitionResults.averageActualLogEntryRate, - }, - ]; - }, - [] - ); -}; - -export const getTopAnomalyScoresByPartition = ( - results: GetLogEntryRateSuccessResponsePayload['data'] -) => { - return results.histogramBuckets.reduce>((topScores, bucket) => { - bucket.partitions.forEach(partition => { - if (partition.maximumAnomalyScore > 0) { - topScores = { - ...topScores, - [partition.partitionId]: - !topScores[partition.partitionId] || - partition.maximumAnomalyScore > topScores[partition.partitionId] - ? partition.maximumAnomalyScore - : topScores[partition.partitionId], - }; - } - }); - return topScores; - }, {}); +export const getLogEntryRateSeriesForPartition = (results: LogRateResults, partitionId: string) => { + return results.partitionBuckets[partitionId].buckets.reduce< + Array<{ time: number; value: number }> + >((buckets, bucket) => { + return [ + ...buckets, + { + time: bucket.startTime, + value: bucket.averageActualLogEntryRate, + }, + ]; + }, []); }; -export const getAnnotationsForPartition = ( - results: GetLogEntryRateSuccessResponsePayload['data'], - partitionId: string -) => { - return results.histogramBuckets.reduce>( +export const getAnnotationsForPartition = (results: LogRateResults, partitionId: string) => { + return results.partitionBuckets[partitionId].buckets.reduce< + Record + >( (annotatedBucketsBySeverity, bucket) => { - const partitionResults = bucket.partitions.find(partition => { - return ( - partition.partitionId === partitionId || - (partition.partitionId === '' && partitionId === 'unknown') - ); - }); - const severityCategory = partitionResults - ? getSeverityCategoryForScore(partitionResults.maximumAnomalyScore) - : null; - if (!partitionResults || !partitionResults.maximumAnomalyScore || !severityCategory) { + const severityCategory = getSeverityCategoryForScore(bucket.maximumAnomalyScore); + if (!severityCategory) { return annotatedBucketsBySeverity; } @@ -134,7 +88,7 @@ export const getAnnotationsForPartition = ( { defaultMessage: 'Max anomaly score: {maxAnomalyScore}', values: { - maxAnomalyScore: formatAnomalyScore(partitionResults.maximumAnomalyScore), + maxAnomalyScore: formatAnomalyScore(bucket.maximumAnomalyScore), }, } ), @@ -152,29 +106,17 @@ export const getAnnotationsForPartition = ( }; export const getTotalNumberOfLogEntriesForPartition = ( - results: GetLogEntryRateSuccessResponsePayload['data'], + results: LogRateResults, partitionId: string ) => { - return results.histogramBuckets.reduce((sumPartitionNumberOfLogEntries, bucket) => { - const partitionResults = bucket.partitions.find(partition => { - return ( - partition.partitionId === partitionId || - (partition.partitionId === '' && partitionId === 'unknown') - ); - }); - if (!partitionResults || !partitionResults.numberOfLogEntries) { - return sumPartitionNumberOfLogEntries; - } else { - return (sumPartitionNumberOfLogEntries += partitionResults.numberOfLogEntries); - } - }, 0); + return results.partitionBuckets[partitionId].totalNumberOfLogEntries; }; -export const getAnnotationsForAll = (results: GetLogEntryRateSuccessResponsePayload['data']) => { +export const getAnnotationsForAll = (results: LogRateResults) => { return results.histogramBuckets.reduce>( (annotatedBucketsBySeverity, bucket) => { const maxAnomalyScoresByPartition = bucket.partitions.reduce< - Array<{ partitionId: string; maximumAnomalyScore: number }> + Array<{ partitionName: string; maximumAnomalyScore: number }> >((bucketMaxAnomalyScoresByPartition, partition) => { if (!getSeverityCategoryForScore(partition.maximumAnomalyScore)) { return bucketMaxAnomalyScoresByPartition; @@ -182,7 +124,7 @@ export const getAnnotationsForAll = (results: GetLogEntryRateSuccessResponsePayl return [ ...bucketMaxAnomalyScoresByPartition, { - partitionId: partition.partitionId ? partition.partitionId : 'unknown', + partitionName: getFriendlyNameForPartitionId(partition.partitionId), maximumAnomalyScore: formatAnomalyScore(partition.maximumAnomalyScore), }, ]; @@ -227,16 +169,14 @@ export const getAnnotationsForAll = (results: GetLogEntryRateSuccessResponsePayl ); }; -export const getTopAnomalyScoreAcrossAllPartitions = ( - results: GetLogEntryRateSuccessResponsePayload['data'] -) => { - const allMaxScores = results.histogramBuckets.reduce((scores, bucket) => { - const bucketMaxScores = bucket.partitions.reduce((bucketScores, partition) => { - return [...bucketScores, partition.maximumAnomalyScore]; - }, []); - return [...scores, ...bucketMaxScores]; - }, []); - return Math.max(...allMaxScores); +export const getTopAnomalyScoreAcrossAllPartitions = (results: LogRateResults) => { + const allTopScores = Object.values(results.partitionBuckets).reduce( + (scores: number[], partition) => { + return [...scores, partition.topAnomalyScore]; + }, + [] + ); + return Math.max(...allTopScores); }; const getSeverityCategoryForScore = (score: number): MLSeverityScoreCategories | undefined => { @@ -257,3 +197,7 @@ const getSeverityCategoryForScore = (score: number): MLSeverityScoreCategories | export const formatAnomalyScore = (score: number) => { return Math.round(score); }; + +export const getFriendlyNameForPartitionId = (partitionId: string) => { + return partitionId !== '' ? partitionId : 'unknown'; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx index 682eb23fa4774..44805520f3b9e 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/log_rate/index.tsx @@ -8,7 +8,7 @@ import { EuiEmptyPrompt, EuiLoadingSpinner, EuiSpacer, EuiTitle, EuiText } from import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; -import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/http_api/log_analysis/results/log_entry_rate'; +import { LogRateResults as Results } from '../../../../../containers/logs/log_analysis/log_analysis_results'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; import { LogEntryRateBarChart } from './bar_chart'; import { getLogEntryRatePartitionedSeries } from '../helpers/data_formatters'; @@ -21,7 +21,7 @@ export const LogRateResults = ({ timeRange, }: { isLoading: boolean; - results: GetLogEntryRateSuccessResponsePayload['data'] | null; + results: Results | null; setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; }) => { diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index 7e935f3fb73ea..895d95b1471a0 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -5,7 +5,6 @@ */ import { i18n } from '@kbn/i18n'; -import { EuiBetaBadge } from '@elastic/eui'; import React from 'react'; import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { UICapabilities } from 'ui/capabilities'; @@ -14,7 +13,7 @@ import { injectUICapabilities } from 'ui/capabilities/react'; import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; import { Header } from '../../components/header'; -import { RoutedTabs } from '../../components/navigation/routed_tabs'; +import { RoutedTabs, TabBetaBadge } from '../../components/navigation/routed_tabs'; import { ColumnarPage } from '../../components/page'; import { SourceLoadingPage } from '../../components/source_loading_page'; import { SourceErrorPage } from '../../components/source_error_page'; @@ -28,6 +27,7 @@ import { LogAnalysisCapabilities, } from '../../containers/logs/log_analysis'; import { useSourceId } from '../../containers/source_id'; +import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params'; interface LogsPageProps extends RouteComponentProps { uiCapabilities: UICapabilities; @@ -39,76 +39,32 @@ export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPag const logAnalysisCapabilities = useLogAnalysisCapabilities(); const streamTab = { - title: i18n.translate('xpack.infra.logs.index.streamTabTitle', { defaultMessage: 'Stream' }), + title: streamTabTitle, path: `${match.path}/stream`, }; - const analysisBetaBadgeTitle = i18n.translate('xpack.infra.logs.index.analysisBetaBadgeTitle', { - defaultMessage: 'Analysis', - }); - const analysisBetaBadgeLabel = i18n.translate('xpack.infra.logs.index.analysisBetaBadgeLabel', { - defaultMessage: 'Beta', - }); - const analysisBetaBadgeTooltipContent = i18n.translate( - 'xpack.infra.logs.index.analysisBetaBadgeTooltipContent', - { - defaultMessage: - 'This feature is under active development. Extra functionality is coming, and some functionality may change.', - } - ); - const analysisBetaBadge = ( - - ); - const analysisTab = { + + const logRateTab = { title: ( <> - - {i18n.translate('xpack.infra.logs.index.analysisTabTitle', { - defaultMessage: 'Analysis', - })} - - {analysisBetaBadge} + {logRateTabTitle} + ), - path: `${match.path}/analysis`, + path: `${match.path}/log-rate`, }; + const settingsTab = { - title: i18n.translate('xpack.infra.logs.index.settingsTabTitle', { - defaultMessage: 'Settings', - }), + title: settingsTabTitle, path: `${match.path}/settings`, }; - const pageTitle = i18n.translate('xpack.infra.header.logsTitle', { - defaultMessage: 'Logs', - }); + return ( - + - +
    - - - + + + + )} @@ -152,3 +113,25 @@ export const LogsPage = injectUICapabilities(({ match, uiCapabilities }: LogsPag ); }); + +const pageTitle = i18n.translate('xpack.infra.header.logsTitle', { + defaultMessage: 'Logs', +}); + +const streamTabTitle = i18n.translate('xpack.infra.logs.index.streamTabTitle', { + defaultMessage: 'Stream', +}); + +const logRateTabTitle = i18n.translate('xpack.infra.logs.index.analysisBetaBadgeTitle', { + defaultMessage: 'Log Rate', +}); + +const settingsTabTitle = i18n.translate('xpack.infra.logs.index.settingsTabTitle', { + defaultMessage: 'Settings', +}); + +const feedbackLinkText = i18n.translate('xpack.infra.logsPage.logsHelpContent.feedbackLinkText', { + defaultMessage: 'Provide feedback for Logs', +}); + +const feedbackLinkUrl = 'https://discuss.elastic.co/c/logs'; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__fixtures__/index_pattern_response.json b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__fixtures__/index_pattern_response.json index 408e839596ae2..27e480623f311 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__fixtures__/index_pattern_response.json +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__fixtures__/index_pattern_response.json @@ -161,8 +161,7 @@ "searchable": true, "aggregatable": true, "readFromDocValues": true, - "parent": "machine.os", - "subType": "multi" + "subType":{ "multi":{ "parent": "machine.os" } } }, { "name": "geo.src", @@ -277,6 +276,28 @@ "searchable": true, "aggregatable": true, "readFromDocValues": false + }, + { + "name": "nestedField.child", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false, + "subType": { "nested": { "path": "nestedField" } } + }, + { + "name": "nestedField.nestedChild.doublyNestedChild", + "type": "string", + "esTypes": ["text"], + "count": 0, + "scripted": false, + "searchable": true, + "aggregatable": false, + "readFromDocValues": false, + "subType": { "nested": { "path": "nestedField.nestedChild" } } } ] } diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js index d19fb47468829..8a20337317cfb 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/field.js @@ -80,4 +80,60 @@ describe('Kuery field suggestions', function () { expect(suggestion).to.have.property('description'); }); }); + + describe('nested fields', function () { + + it('should automatically wrap nested fields in KQL\'s nested syntax', () => { + const prefix = 'ch'; + const suffix = ''; + const suggestions = getSuggestions({ prefix, suffix }); + + const suggestion = suggestions.find(({ field }) => field.name === 'nestedField.child'); + expect(suggestion.text).to.be('nestedField:{ child }'); + + // For most suggestions the cursor can be placed at the end of the suggestion text, but + // for the nested field syntax we want to place the cursor inside the curly braces + expect(suggestion.cursorIndex).to.be(20); + }); + + it('should narrow suggestions to children of a nested path if provided', () => { + const prefix = 'ch'; + const suffix = ''; + + const allSuggestions = getSuggestions({ prefix, suffix }); + expect(allSuggestions.length).to.be.greaterThan(2); + + const nestedSuggestions = getSuggestions({ prefix, suffix, nestedPath: 'nestedField' }); + expect(nestedSuggestions).to.have.length(2); + }); + + it('should not wrap the suggestion in KQL\'s nested syntax if the correct nested path is already provided', () => { + const prefix = 'ch'; + const suffix = ''; + + const suggestions = getSuggestions({ prefix, suffix, nestedPath: 'nestedField' }); + const suggestion = suggestions.find(({ field }) => field.name === 'nestedField.child'); + expect(suggestion.text).to.be('child '); + }); + + it('should handle fields nested multiple levels deep', () => { + const prefix = 'doubly'; + const suffix = ''; + + const suggestionsWithNoPath = getSuggestions({ prefix, suffix }); + expect(suggestionsWithNoPath).to.have.length(1); + const [ noPathSuggestion ] = suggestionsWithNoPath; + expect(noPathSuggestion.text).to.be('nestedField.nestedChild:{ doublyNestedChild }'); + + const suggestionsWithPartialPath = getSuggestions({ prefix, suffix, nestedPath: 'nestedField' }); + expect(suggestionsWithPartialPath).to.have.length(1); + const [ partialPathSuggestion ] = suggestionsWithPartialPath; + expect(partialPathSuggestion.text).to.be('nestedChild:{ doublyNestedChild }'); + + const suggestionsWithFullPath = getSuggestions({ prefix, suffix, nestedPath: 'nestedField.nestedChild' }); + expect(suggestionsWithFullPath).to.have.length(1); + const [ fullPathSuggestion ] = suggestionsWithFullPath; + expect(fullPathSuggestion.text).to.be('doublyNestedChild '); + }); + }); }); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/operator.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/operator.js index 57d38ddab5c3d..bb20e927039c0 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/operator.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/__tests__/operator.js @@ -56,4 +56,9 @@ describe('Kuery operator suggestions', function () { expect(suggestion).to.have.property('description'); }); }); + + it('should handle nested paths', () => { + const suggestions = getSuggestions({ fieldName: 'child', nestedPath: 'nestedField' }); + expect(suggestions.length).to.be.greaterThan(0); + }); }); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js index 4f62c6344e9a0..3d7e1979d224b 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js @@ -29,15 +29,28 @@ export function getSuggestionsProvider({ indexPatterns }) { const allFields = flatten(indexPatterns.map(indexPattern => { return indexPattern.fields.filter(isFilterable); })); - return function getFieldSuggestions({ start, end, prefix, suffix }) { - const search = `${prefix}${suffix}`.toLowerCase(); - const fieldNames = allFields.map(field => field.name); - const matchingFieldNames = fieldNames.filter(name => name.toLowerCase().includes(search)); - const sortedFieldNames = sortPrefixFirst(matchingFieldNames.sort(keywordComparator), search); - const suggestions = sortedFieldNames.map(fieldName => { - const text = `${escapeKuery(fieldName)} `; - const description = getDescription(fieldName); - return { type, text, description, start, end }; + return function getFieldSuggestions({ start, end, prefix, suffix, nestedPath = '' }) { + const search = `${prefix}${suffix}`.trim().toLowerCase(); + const matchingFields = allFields.filter(field => { + return ( + !nestedPath + || (nestedPath && (field.subType && field.subType.nested && field.subType.nested.path.includes(nestedPath))) + ) + && field.name.toLowerCase().includes(search) && field.name !== search; + }); + const sortedFields = sortPrefixFirst(matchingFields.sort(keywordComparator), search, 'name'); + const suggestions = sortedFields.map(field => { + const remainingPath = field.subType && field.subType.nested + ? field.subType.nested.path.slice(nestedPath ? nestedPath.length + 1 : 0) + : ''; + const text = field.subType && field.subType.nested && remainingPath.length > 0 + ? `${escapeKuery(remainingPath)}:{ ${escapeKuery(field.name.slice(field.subType.nested.path.length + 1))} }` + : `${escapeKuery(field.name.slice(nestedPath ? nestedPath.length + 1 : 0))} `; + const description = getDescription(field.name); + const cursorIndex = field.subType && field.subType.nested && remainingPath.length > 0 + ? text.length - 2 + : text.length; + return { type, text, description, start, end, cursorIndex, field }; }); return suggestions; }; @@ -45,10 +58,10 @@ export function getSuggestionsProvider({ indexPatterns }) { function keywordComparator(first, second) { const extensions = ['raw', 'keyword']; - if (extensions.map(ext => `${first}.${ext}`).includes(second)) { + if (extensions.map(ext => `${first.name}.${ext}`).includes(second.name)) { return 1; - } else if (extensions.map(ext => `${second}.${ext}`).includes(first)) { + } else if (extensions.map(ext => `${second.name}.${ext}`).includes(first.name)) { return -1; } - return first.localeCompare(second); + return first.name.localeCompare(second.name); } diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/operator.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/operator.js index a60216bd9316a..3955fdfd9afe7 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/operator.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/operator.js @@ -123,8 +123,9 @@ export function getSuggestionsProvider({ indexPatterns }) { const allFields = flatten(indexPatterns.map(indexPattern => { return indexPattern.fields.slice(); })); - return function getOperatorSuggestions({ end, fieldName }) { - const fields = allFields.filter(field => field.name === fieldName); + return function getOperatorSuggestions({ end, fieldName, nestedPath }) { + const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; + const fields = allFields.filter(field => field.name === fullFieldName); return flatten(fields.map(field => { const matchingOperators = Object.keys(operators).filter(operator => { const { fieldTypes } = operators[operator]; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js index 66e62e884e9b3..52bf347a94075 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.js @@ -26,9 +26,11 @@ export function getSuggestionsProvider({ indexPatterns, boolFilter }) { prefix, suffix, fieldName, + nestedPath, }) { - const fields = allFields.filter(field => field.name === fieldName); - const query = `${prefix}${suffix}`; + const fullFieldName = nestedPath ? `${nestedPath}.${fieldName}` : fieldName; + const fields = allFields.filter(field => field.name === fullFieldName); + const query = `${prefix}${suffix}`.trim(); const { getSuggestions } = npStart.plugins.data; const suggestionsByField = fields.map(field => { diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js index c59917ebdc3bf..591833d646360 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/value.test.js @@ -19,7 +19,11 @@ jest.mock('ui/new_platform', () => ({ res = [true, false]; } else if (field.name === 'machine.os') { res = ['Windo"ws', 'Mac\'', 'Linux']; - } else { + } + else if (field.name === 'nestedField.child') { + res = ['foo']; + } + else { res = []; } return Promise.resolve(res); @@ -67,6 +71,17 @@ describe('Kuery value suggestions', function () { expect(suggestions[0].end).toEqual(end); }); + test('should handle nested paths', async () => { + const suggestions = await getSuggestions({ + fieldName: 'child', + nestedPath: 'nestedField', + prefix: '', + suffix: '', + }); + expect(suggestions.length).toEqual(1); + expect(suggestions[0].text).toEqual('"foo" '); + }); + describe('Boolean suggestions', function () { test('should stringify boolean fields', async () => { const fieldName = 'ssl'; diff --git a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx index e350e36b3bdc0..30c955d45289b 100644 --- a/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx +++ b/x-pack/legacy/plugins/lens/public/datatable_visualization_plugin/expression.tsx @@ -8,7 +8,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; import { EuiBasicTable } from '@elastic/eui'; -import { ExpressionFunction } from '../../../../../../src/plugins/expressions/common'; +import { ExpressionFunction } from '../../../../../../src/plugins/expressions/public'; import { KibanaDatatable } from '../../../../../../src/legacy/core_plugins/interpreter/public'; import { LensMultiTable } from '../types'; import { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts index 566e5bece096d..511f2b11c4794 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/auto_date.ts @@ -9,7 +9,7 @@ import dateMath from '@elastic/datemath'; import { ExpressionFunction, KibanaContext, -} from '../../../../../../src/plugins/expressions/common'; +} from '../../../../../../src/plugins/expressions/public'; import { DateRange } from '../../common'; interface LensAutoDateProps { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx index 4dde289259c41..11bc52fc48378 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/plugin.tsx @@ -4,17 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Registry } from '@kbn/interpreter/target/common'; import { CoreSetup } from 'src/core/public'; // The following dependencies on ui/* and src/legacy/core_plugins must be mocked when testing import chrome, { Chrome } from 'ui/chrome'; import { npSetup, npStart } from 'ui/new_platform'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; -import { ExpressionFunction } from '../../../../../../src/legacy/core_plugins/interpreter/public'; -import { functionsRegistry } from '../../../../../../src/legacy/core_plugins/interpreter/public/registries'; import { getIndexPatternDatasource } from './indexpattern'; import { renameColumns } from './rename_columns'; import { autoDate } from './auto_date'; +import { ExpressionsSetup } from '../../../../../../src/plugins/expressions/public'; // TODO these are intermediary types because interpreter is not typed yet // They can get replaced by references to the real interfaces as soon as they @@ -22,22 +20,15 @@ import { autoDate } from './auto_date'; export interface IndexPatternDatasourceSetupPlugins { chrome: Chrome; - interpreter: InterpreterSetup; -} - -export interface InterpreterSetup { - functionsRegistry: Registry< - ExpressionFunction, - ExpressionFunction - >; + expressions: ExpressionsSetup; } class IndexPatternDatasourcePlugin { constructor() {} - setup(core: CoreSetup, { interpreter }: IndexPatternDatasourceSetupPlugins) { - interpreter.functionsRegistry.register(() => renameColumns); - interpreter.functionsRegistry.register(() => autoDate); + setup(core: CoreSetup, { expressions }: IndexPatternDatasourceSetupPlugins) { + expressions.registerFunction(renameColumns); + expressions.registerFunction(autoDate); } stop() {} @@ -48,9 +39,7 @@ const plugin = new IndexPatternDatasourcePlugin(); export const indexPatternDatasourceSetup = () => { plugin.setup(npSetup.core, { chrome, - interpreter: { - functionsRegistry, - }, + expressions: npSetup.plugins.expressions, }); return getIndexPatternDatasource({ diff --git a/x-pack/legacy/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/legacy/plugins/lens/server/routes/existing_fields.test.ts index de2da49fd5c0e..1647dcccaed3c 100644 --- a/x-pack/legacy/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/legacy/plugins/lens/server/routes/existing_fields.test.ts @@ -10,7 +10,7 @@ describe('existingFields', () => { function field(name: string, parent?: string) { return { name, - parent, + subType: parent ? { multi: { parent } } : undefined, aggregatable: true, esTypes: [], readFromDocValues: true, diff --git a/x-pack/legacy/plugins/lens/server/routes/existing_fields.ts b/x-pack/legacy/plugins/lens/server/routes/existing_fields.ts index 31edb45b5de55..ad1af966983fb 100644 --- a/x-pack/legacy/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/legacy/plugins/lens/server/routes/existing_fields.ts @@ -109,11 +109,14 @@ export function existingFields( docs: Array<{ _source: Document }>, fields: FieldDescriptor[] ): string[] { - const allFields = fields.map(field => ({ - name: field.name, - parent: field.parent, - path: (field.parent || field.name).split('.'), - })); + const allFields = fields.map(field => { + const parent = field.subType && field.subType.multi && field.subType.multi.parent; + return { + name: field.name, + parent, + path: (parent || field.name).split('.'), + }; + }); const missingFields = new Set(allFields); for (const doc of docs) { diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js index dbd6eb6ffb39c..509c652471ffa 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js @@ -14,7 +14,8 @@ import { getMapReady, getWaitingForMapReadyLayerListRaw, getTransientLayerId, - getTooltipState + getTooltipState, + getQuery, } from '../selectors/map_selectors'; import { FLYOUT_STATE } from '../reducers/ui'; import { @@ -645,15 +646,25 @@ function removeLayerFromLayerList(layerId) { }; } -export function setQuery({ query, timeFilters, filters = [] }) { +export function setQuery({ query, timeFilters, filters = [], refresh = false }) { + function generateQueryTimestamp() { + return (new Date()).toISOString(); + } return async (dispatch, getState) => { + const prevQuery = getQuery(getState()); + const prevTriggeredAt = (prevQuery && prevQuery.queryLastTriggeredAt) + ? prevQuery.queryLastTriggeredAt + : generateQueryTimestamp(); + dispatch({ type: SET_QUERY, timeFilters, query: { ...query, - // ensure query changes to trigger re-fetch even when query is the same because "Refresh" clicked - queryLastTriggeredAt: (new Date()).toISOString(), + // ensure query changes to trigger re-fetch when "Refresh" clicked + queryLastTriggeredAt: refresh + ? generateQueryTimestamp() + : prevTriggeredAt, }, filters, }); diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js index b15b94a49cebc..5dc08751347e4 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js @@ -44,7 +44,6 @@ describe('kibana.yml configured with map.tilemap.url', () => { expect(layers).toEqual([{ alpha: 1, __dataRequests: [], - __injectedData: null, id: layers[0].id, applyGlobalQuery: true, label: null, @@ -87,7 +86,6 @@ describe('EMS is enabled', () => { expect(layers).toEqual([{ alpha: 1, __dataRequests: [], - __injectedData: null, id: layers[0].id, applyGlobalQuery: true, label: null, diff --git a/x-pack/legacy/plugins/maps/public/angular/map.html b/x-pack/legacy/plugins/maps/public/angular/map.html index 7c4f9cb018e33..90d4ddbeb0092 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map.html +++ b/x-pack/legacy/plugins/maps/public/angular/map.html @@ -11,7 +11,7 @@ show-save-query="showSaveQuery" query="query" saved-query="savedQuery" - on-query-submit="updateQueryAndDispatch" + on-query-submit="onQuerySubmit" filters="filters" on-filters-updated="updateFiltersAndDispatch" index-patterns="indexPatterns" diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index cef2a70a6d03d..594548333cb8c 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -198,7 +198,7 @@ app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppSta } }); /* End of Saved Queries */ - async function onQueryChange({ filters, query, time }) { + async function onQueryChange({ filters, query, time, refresh }) { if (filters) { filterManager.setFilters(filters); // Maps and merges filters $scope.filters = filterManager.getFilters(); @@ -210,22 +210,24 @@ app.controller('GisMapController', ($scope, $route, kbnUrl, localStorage, AppSta $scope.time = time; } syncAppAndGlobalState(); - dispatchSetQuery(); + dispatchSetQuery(refresh); } - function dispatchSetQuery() { + function dispatchSetQuery(refresh) { store.dispatch(setQuery({ filters: $scope.filters, query: $scope.query, - timeFilters: $scope.time + timeFilters: $scope.time, + refresh, })); } $scope.indexPatterns = []; - $scope.updateQueryAndDispatch = function ({ dateRange, query }) { + $scope.onQuerySubmit = function ({ dateRange, query }) { onQueryChange({ query, time: dateRange, + refresh: true, }); }; $scope.updateFiltersAndDispatch = function (filters) { diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js index 3cff39f684a28..18b8dba6702d4 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js @@ -72,7 +72,7 @@ export class MapEmbeddable extends Embeddable { } } - _dispatchSetQuery({ query, timeRange, filters }) { + _dispatchSetQuery({ query, timeRange, filters, refresh }) { this._prevTimeRange = timeRange; this._prevQuery = query; this._prevFilters = filters; @@ -80,6 +80,7 @@ export class MapEmbeddable extends Embeddable { filters: filters.filter(filter => !filter.meta.disabled), query, timeFilters: timeRange, + refresh, })); } @@ -165,7 +166,8 @@ export class MapEmbeddable extends Embeddable { this._dispatchSetQuery({ query: this._prevQuery, timeRange: this._prevTimeRange, - filters: this._prevFilters + filters: this._prevFilters, + refresh: true }); } diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.js b/x-pack/legacy/plugins/maps/public/index_pattern_util.js index ca43826f307eb..9a0258592a606 100644 --- a/x-pack/legacy/plugins/maps/public/index_pattern_util.js +++ b/x-pack/legacy/plugins/maps/public/index_pattern_util.js @@ -28,6 +28,6 @@ export function getTermsFields(fields) { export function getSourceFields(fields) { return fields.filter(field => { // Multi fields are not stored in _source and only exist in index. - return field.subType !== 'multi'; + return field.subType && field.subType.multi; }); } diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index c8187fd83ee4d..45854b7b729f5 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -9,7 +9,6 @@ import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import turf from 'turf'; import turfBooleanContains from '@turf/boolean-contains'; import { DataRequest } from './util/data_request'; -import { InjectedData } from './util/injected_data'; import { MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER, SOURCE_DATA_ID_ORIGIN @@ -32,11 +31,6 @@ export class AbstractLayer { } else { this._dataRequests = []; } - if (this._descriptor.__injectedData) { - this._injectedData = new InjectedData(this._descriptor.__injectedData); - } else { - this._injectedData = null; - } } static getBoundDataForSource(mbMap, sourceId) { @@ -48,7 +42,6 @@ export class AbstractLayer { const layerDescriptor = { ...options }; layerDescriptor.__dataRequests = _.get(options, '__dataRequests', []); - layerDescriptor.__injectedData = _.get(options, '__injectedData', null); layerDescriptor.id = _.get(options, 'id', uuid()); layerDescriptor.label = options.label && options.label.length > 0 ? options.label : null; layerDescriptor.minZoom = _.get(options, 'minZoom', 0); @@ -287,10 +280,6 @@ export class AbstractLayer { return this._dataRequests.find(dataRequest => dataRequest.getDataId() === id); } - getInjectedData() { - return this._injectedData ? this._injectedData.getData() : null; - } - isLayerLoading() { return this._dataRequests.some(dataRequest => dataRequest.isLoading()); } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js index cf876a59d0be4..59cfc7b486bdd 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js @@ -35,9 +35,32 @@ export class GeojsonFileSource extends AbstractVectorSource { applyGlobalQuery: DEFAULT_APPLY_GLOBAL_QUERY } - static createDescriptor(name) { + static createDescriptor(geoJson, name) { + // Wrap feature as feature collection if needed + let featureCollection; + + if (!geoJson) { + featureCollection = { + type: 'FeatureCollection', + features: [] + }; + } else if (geoJson.type === 'FeatureCollection') { + featureCollection = geoJson; + } else if (geoJson.type === 'Feature') { + featureCollection = { + type: 'FeatureCollection', + features: [geoJson] + }; + } else { // Missing or incorrect type + featureCollection = { + type: 'FeatureCollection', + features: [] + }; + } + return { type: GeojsonFileSource.type, + __featureCollection: featureCollection, name }; } @@ -85,16 +108,9 @@ export class GeojsonFileSource extends AbstractVectorSource { onPreviewSource(null); return; } - const sourceDescriptor = GeojsonFileSource.createDescriptor(name); + const sourceDescriptor = GeojsonFileSource.createDescriptor(geojsonFile, name); const source = new GeojsonFileSource(sourceDescriptor, inspectorAdapters); - const featureCollection = (geojsonFile.type === 'Feature') - ? { - type: 'FeatureCollection', - features: [{ ...geojsonFile }] - } - : geojsonFile; - - onPreviewSource(source, { __injectedData: featureCollection }); + onPreviewSource(source); }; }; @@ -125,6 +141,22 @@ export class GeojsonFileSource extends AbstractVectorSource { ); } + async getGeoJsonWithMeta() { + const copiedPropsFeatures = this._descriptor.__featureCollection.features + .map(feature => ({ + type: 'Feature', + geometry: feature.geometry, + properties: feature.properties ? { ...feature.properties } : {} + })); + return { + data: { + type: 'FeatureCollection', + features: copiedPropsFeatures + }, + meta: {} + }; + } + async getDisplayName() { return this._descriptor.name; } @@ -136,8 +168,4 @@ export class GeojsonFileSource extends AbstractVectorSource { shouldBeIndexed() { return GeojsonFileSource.isIndexingSource; } - - isInjectedData() { - return true; - } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js new file mode 100644 index 0000000000000..d9639144dfc52 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js @@ -0,0 +1,142 @@ +/* + * 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 { AbstractESSource } from './es_source'; +import { ESAggMetricTooltipProperty } from '../tooltips/es_aggmetric_tooltip_property'; +import { METRIC_TYPE } from '../../../common/constants'; +import _ from 'lodash'; + +const COUNT_PROP_LABEL = 'count'; +const COUNT_PROP_NAME = 'doc_count'; +const AGG_DELIMITER = '_of_'; + +export class AbstractESAggSource extends AbstractESSource { + + static METRIC_SCHEMA_CONFIG = { + group: 'metrics', + name: 'metric', + title: 'Value', + min: 1, + max: Infinity, + aggFilter: [ + METRIC_TYPE.AVG, + METRIC_TYPE.COUNT, + METRIC_TYPE.MAX, + METRIC_TYPE.MIN, + METRIC_TYPE.SUM, + METRIC_TYPE.UNIQUE_COUNT + ], + defaults: [ + { schema: 'metric', type: METRIC_TYPE.COUNT } + ] + }; + + _formatMetricKey(metric) { + const aggType = metric.type; + const fieldName = metric.field; + return aggType !== METRIC_TYPE.COUNT ? `${aggType}${AGG_DELIMITER}${fieldName}` : COUNT_PROP_NAME; + } + + _formatMetricLabel(metric) { + const aggType = metric.type; + const fieldName = metric.field; + return aggType !== METRIC_TYPE.COUNT ? `${aggType} of ${fieldName}` : COUNT_PROP_LABEL; + } + + _getValidMetrics() { + const metrics = _.get(this._descriptor, 'metrics', []).filter(({ type, field }) => { + if (type === METRIC_TYPE.COUNT) { + return true; + } + + if (field) { + return true; + } + return false; + }); + if (metrics.length === 0) { + metrics.push({ type: METRIC_TYPE.COUNT }); + } + return metrics; + } + + getMetricFields() { + return this._getValidMetrics().map(metric => { + const metricKey = this._formatMetricKey(metric); + const metricLabel = metric.label ? metric.label : this._formatMetricLabel(metric); + const metricCopy = { ...metric }; + delete metricCopy.label; + return { + ...metricCopy, + propertyKey: metricKey, + propertyLabel: metricLabel + }; + }); + } + + async getNumberFields() { + return this.getMetricFields().map(({ propertyKey: name, propertyLabel: label }) => { + return { label, name }; + }); + } + + getFieldNames() { + return this.getMetricFields().map(({ propertyKey }) => { + return propertyKey; + }); + } + + createMetricAggConfigs() { + return this.getMetricFields().map(metric => { + const metricAggConfig = { + id: metric.propertyKey, + enabled: true, + type: metric.type, + schema: 'metric', + params: {} + }; + if (metric.type !== METRIC_TYPE.COUNT) { + metricAggConfig.params = { field: metric.field }; + } + return metricAggConfig; + }); + } + + async filterAndFormatPropertiesToHtmlForMetricFields(properties) { + let indexPattern; + try { + indexPattern = await this._getIndexPattern(); + } catch(error) { + console.warn(`Unable to find Index pattern ${this._descriptor.indexPatternId}, values are not formatted`); + return properties; + } + + const metricFields = this.getMetricFields(); + const tooltipProperties = []; + metricFields.forEach((metricField) => { + let value; + for (const key in properties) { + if (properties.hasOwnProperty(key) && metricField.propertyKey === key) { + value = properties[key]; + break; + } + } + + const tooltipProperty = new ESAggMetricTooltipProperty( + metricField.propertyKey, + metricField.propertyLabel, + value, + indexPattern, + metricField + ); + tooltipProperties.push(tooltipProperty); + }); + + return tooltipProperties; + + } + +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js index e06568285dd6b..c83f12ce992ff 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js @@ -20,8 +20,7 @@ export function convertToGeoJson({ table, renderAs }) { } const metricColumns = table.columns.filter(column => { - return column.aggConfig.type.type === 'metrics' - && column.aggConfig.type.dslName !== 'geo_centroid'; + return column.aggConfig.type.type === 'metrics' && column.aggConfig.type.dslName !== 'geo_centroid'; }); const geocentroidColumn = table.columns.find(column => column.aggConfig.type.dslName === 'geo_centroid'); if (!geocentroidColumn) { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index 776980e17bb13..188ea810258d2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -8,7 +8,6 @@ import React from 'react'; import uuid from 'uuid/v4'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; -import { AbstractESSource } from '../es_source'; import { HeatmapLayer } from '../../heatmap_layer'; import { VectorLayer } from '../../vector_layer'; import { Schemas } from 'ui/vis/editors/default/schemas'; @@ -21,33 +20,17 @@ import { RENDER_AS } from './render_as'; import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; import { GRID_RESOLUTION } from '../../grid_resolution'; -import { SOURCE_DATA_ID_ORIGIN, ES_GEO_GRID, METRIC_TYPE } from '../../../../common/constants'; +import { SOURCE_DATA_ID_ORIGIN, ES_GEO_GRID } from '../../../../common/constants'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; +import { AbstractESAggSource } from '../es_agg_source'; const COUNT_PROP_LABEL = 'count'; const COUNT_PROP_NAME = 'doc_count'; const MAX_GEOTILE_LEVEL = 29; const aggSchemas = new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Value', - min: 1, - max: Infinity, - aggFilter: [ - METRIC_TYPE.AVG, - METRIC_TYPE.COUNT, - METRIC_TYPE.MAX, - METRIC_TYPE.MIN, - METRIC_TYPE.SUM, - METRIC_TYPE.UNIQUE_COUNT - ], - defaults: [ - { schema: 'metric', type: METRIC_TYPE.COUNT } - ] - }, + AbstractESAggSource.METRIC_SCHEMA_CONFIG, { group: 'buckets', name: 'segment', @@ -58,7 +41,7 @@ const aggSchemas = new Schemas([ } ]); -export class ESGeoGridSource extends AbstractESSource { +export class ESGeoGridSource extends AbstractESAggSource { static type = ES_GEO_GRID; static title = i18n.translate('xpack.maps.source.esGridTitle', { @@ -140,12 +123,6 @@ export class ESGeoGridSource extends AbstractESSource { ]; } - getFieldNames() { - return this.getMetricFields().map(({ propertyKey }) => { - return propertyKey; - }); - } - isGeoGridPrecisionAware() { return true; } @@ -184,12 +161,6 @@ export class ESGeoGridSource extends AbstractESSource { })); } - async getNumberFields() { - return this.getMetricFields().map(({ propertyKey: name, propertyLabel: label }) => { - return { label, name }; - }); - } - async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) { const indexPattern = await this._getIndexPattern(); const searchSource = await this._makeSearchSource(searchFilters, 0); @@ -221,29 +192,8 @@ export class ESGeoGridSource extends AbstractESSource { return true; } - _formatMetricKey(metric) { - return metric.type !== METRIC_TYPE.COUNT ? `${metric.type}_of_${metric.field}` : COUNT_PROP_NAME; - } - - _formatMetricLabel(metric) { - return metric.type !== METRIC_TYPE.COUNT ? `${metric.type} of ${metric.field}` : COUNT_PROP_LABEL; - } - _makeAggConfigs(precision) { - const metricAggConfigs = this.getMetricFields().map(metric => { - const metricAggConfig = { - id: metric.propertyKey, - enabled: true, - type: metric.type, - schema: 'metric', - params: {} - }; - if (metric.type !== METRIC_TYPE.COUNT) { - metricAggConfig.params = { field: metric.field }; - } - return metricAggConfig; - }); - + const metricAggConfigs = this.createMetricAggConfigs(); return [ ...metricAggConfigs, { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index 3debfdf1541f7..b55a94669adca 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -8,45 +8,26 @@ import React from 'react'; import uuid from 'uuid/v4'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; -import { AbstractESSource } from '../es_source'; import { VectorLayer } from '../../vector_layer'; import { CreateSourceEditor } from './create_source_editor'; import { UpdateSourceEditor } from './update_source_editor'; import { VectorStyle } from '../../styles/vector_style'; import { vectorStyles } from '../../styles/vector_style_defaults'; import { i18n } from '@kbn/i18n'; -import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW, METRIC_TYPE } from '../../../../common/constants'; +import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW } from '../../../../common/constants'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { convertToLines } from './convert_to_lines'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggConfigs } from 'ui/agg_types'; +import { AbstractESAggSource } from '../es_agg_source'; const COUNT_PROP_LABEL = 'count'; const COUNT_PROP_NAME = 'doc_count'; const MAX_GEOTILE_LEVEL = 29; -const aggSchemas = new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Value', - min: 1, - max: Infinity, - aggFilter: [ - METRIC_TYPE.AVG, - METRIC_TYPE.COUNT, - METRIC_TYPE.MAX, - METRIC_TYPE.MIN, - METRIC_TYPE.SUM, - METRIC_TYPE.UNIQUE_COUNT - ], - defaults: [ - { schema: 'metric', type: METRIC_TYPE.COUNT } - ] - } -]); +const aggSchemas = new Schemas([AbstractESAggSource.METRIC_SCHEMA_CONFIG]); -export class ESPewPewSource extends AbstractESSource { +export class ESPewPewSource extends AbstractESAggSource { static type = ES_PEW_PEW; static title = i18n.translate('xpack.maps.source.pewPewTitle', { @@ -103,12 +84,6 @@ export class ESPewPewSource extends AbstractESSource { return true; } - async getNumberFields() { - return this.getMetricFields().map(({ propertyKey: name, propertyLabel: label }) => { - return { label, name }; - }); - } - async getSupportedShapeTypes() { return [VECTOR_SHAPE_TYPES.LINE]; } @@ -192,19 +167,7 @@ export class ESPewPewSource extends AbstractESSource { async getGeoJsonWithMeta(layerName, searchFilters, registerCancelCallback) { const indexPattern = await this._getIndexPattern(); - const metricAggConfigs = this.getMetricFields().map(metric => { - const metricAggConfig = { - id: metric.propertyKey, - enabled: true, - type: metric.type, - schema: 'metric', - params: {} - }; - if (metric.type !== METRIC_TYPE.COUNT) { - metricAggConfig.params = { field: metric.field }; - } - return metricAggConfig; - }); + const metricAggConfigs = this.createMetricAggConfigs(); const aggConfigs = new AggConfigs(indexPattern, metricAggConfigs, aggSchemas.all); const searchSource = await this._makeSearchSource(searchFilters, 0); @@ -258,14 +221,6 @@ export class ESPewPewSource extends AbstractESSource { }; } - _formatMetricKey(metric) { - return metric.type !== METRIC_TYPE.COUNT ? `${metric.type}_of_${metric.field}` : COUNT_PROP_NAME; - } - - _formatMetricLabel(metric) { - return metric.type !== METRIC_TYPE.COUNT ? `${metric.type} of ${metric.field}` : COUNT_PROP_LABEL; - } - async _getGeoField() { const indexPattern = await this._getIndexPattern(); const geoField = indexPattern.fields.getByName(this._descriptor.destGeoField); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js index 85c866479a6ba..cc53ee27af471 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js @@ -15,8 +15,6 @@ import { timefilter } from 'ui/timefilter'; import _ from 'lodash'; import { AggConfigs } from 'ui/agg_types'; import { i18n } from '@kbn/i18n'; -import { ESAggMetricTooltipProperty } from '../tooltips/es_aggmetric_tooltip_property'; - import uuid from 'uuid/v4'; import { copyPersistentState } from '../../reducers/util'; import { ES_GEO_FIELD_TYPE, METRIC_TYPE } from '../../../common/constants'; @@ -57,80 +55,6 @@ export class AbstractESSource extends AbstractVectorSource { return clonedDescriptor; } - _getValidMetrics() { - const metrics = _.get(this._descriptor, 'metrics', []).filter(({ type, field }) => { - if (type === METRIC_TYPE.COUNT) { - return true; - } - - if (field) { - return true; - } - return false; - }); - if (metrics.length === 0) { - metrics.push({ type: METRIC_TYPE.COUNT }); - } - return metrics; - } - - _formatMetricKey() { - throw new Error('should implement'); - } - - _formatMetricLabel() { - throw new Error('should implement'); - } - - getMetricFields() { - return this._getValidMetrics().map(metric => { - const metricKey = this._formatMetricKey(metric); - const metricLabel = metric.label ? metric.label : this._formatMetricLabel(metric); - const metricCopy = { ...metric }; - delete metricCopy.label; - return { - ...metricCopy, - propertyKey: metricKey, - propertyLabel: metricLabel - }; - }); - } - - async filterAndFormatPropertiesToHtmlForMetricFields(properties) { - let indexPattern; - try { - indexPattern = await this._getIndexPattern(); - } catch(error) { - console.warn(`Unable to find Index pattern ${this._descriptor.indexPatternId}, values are not formatted`); - return properties; - } - - - const metricFields = this.getMetricFields(); - const tooltipProperties = []; - metricFields.forEach((metricField) => { - let value; - for (const key in properties) { - if (properties.hasOwnProperty(key) && metricField.propertyKey === key) { - value = properties[key]; - break; - } - } - - const tooltipProperty = new ESAggMetricTooltipProperty( - metricField.propertyKey, - metricField.propertyLabel, - value, - indexPattern, - metricField - ); - tooltipProperties.push(tooltipProperty); - }); - - return tooltipProperties; - - } - async _runEsQuery(requestName, searchSource, registerCancelCallback, requestDescription) { const abortController = new AbortController(); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js index 1f5adc00cca6f..7d1ccf7373cf6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js @@ -6,34 +6,17 @@ import _ from 'lodash'; -import { AbstractESSource } from './es_source'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggConfigs } from 'ui/agg_types'; import { i18n } from '@kbn/i18n'; import { ESTooltipProperty } from '../tooltips/es_tooltip_property'; import { ES_SIZE_LIMIT, METRIC_TYPE } from '../../../common/constants'; +import { AbstractESAggSource } from './es_agg_source'; const TERMS_AGG_NAME = 'join'; const aggSchemas = new Schemas([ - { - group: 'metrics', - name: 'metric', - title: 'Value', - min: 1, - max: Infinity, - aggFilter: [ - METRIC_TYPE.AVG, - METRIC_TYPE.COUNT, - METRIC_TYPE.MAX, - METRIC_TYPE.MIN, - METRIC_TYPE.SUM, - METRIC_TYPE.UNIQUE_COUNT - ], - defaults: [ - { schema: 'metric', type: METRIC_TYPE.COUNT } - ] - }, + AbstractESAggSource.METRIC_SCHEMA_CONFIG, { group: 'buckets', name: 'segment', @@ -61,7 +44,7 @@ export function extractPropertiesMap(rawEsData, propertyNames, countPropertyName return propertiesMap; } -export class ESTermSource extends AbstractESSource { +export class ESTermSource extends AbstractESAggSource { static type = 'ES_TERM_SOURCE'; @@ -156,20 +139,7 @@ export class ESTermSource extends AbstractESSource { } _makeAggConfigs() { - const metricAggConfigs = this.getMetricFields().map(metric => { - const metricAggConfig = { - id: metric.propertyKey, - enabled: true, - type: metric.type, - schema: 'metric', - params: {} - }; - if (metric.type !== METRIC_TYPE.COUNT) { - metricAggConfig.params = { field: metric.field }; - } - return metricAggConfig; - }); - + const metricAggConfigs = this.createMetricAggConfigs(); return [ ...metricAggConfigs, { @@ -205,10 +175,4 @@ export class ESTermSource extends AbstractESSource { return null; } } - - getFieldNames() { - return this.getMetricFields().map(({ propertyKey }) => { - return propertyKey; - }); - } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/legacy/plugins/maps/public/layers/sources/source.js index f96fc42f02178..3bee49cff6d18 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/source.js @@ -115,10 +115,6 @@ export class AbstractSource { return AbstractSource.isIndexingSource; } - isInjectedData() { - return false; - } - supportsElasticsearchFilters() { return false; } diff --git a/x-pack/legacy/plugins/maps/public/layers/util/injected_data.js b/x-pack/legacy/plugins/maps/public/layers/util/injected_data.js deleted file mode 100644 index 8c18819e9f8b5..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/util/injected_data.js +++ /dev/null @@ -1,21 +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 class InjectedData { - - constructor(data) { - this._descriptor = { data }; - } - - getData() { - return this._descriptor.data; - } - - hasData() { - return !!this._descriptor.data; - } - -} - diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 81f4f3b388d56..7372b549f6423 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -132,18 +132,6 @@ export class VectorLayer extends AbstractLayer { return true; } - getInjectedData() { - const featureCollection = super.getInjectedData(); - if (!featureCollection) { - return null; - } - // Set default visible property on data - featureCollection.features.forEach( - feature => _.set(feature, `properties.${FEATURE_VISIBLE_PROPERTY_NAME}`, true) - ); - return featureCollection; - } - getCustomIconAndTooltipContent() { const featureCollection = this._getSourceFeatureCollection(); @@ -510,16 +498,7 @@ export class VectorLayer extends AbstractLayer { startLoading, stopLoading, onLoadError, registerCancelCallback, dataFilters }) { - if (this._source.isInjectedData()) { - const featureCollection = this.getInjectedData(); - return { - refreshed: false, - featureCollection - }; - } - const requestToken = Symbol(`layer-source-refresh:${ this.getId()} - source`); - const searchFilters = this._getSearchFilters(dataFilters); const canSkip = await this._canSkipSourceUpdate(this._source, SOURCE_DATA_ID_ORIGIN, searchFilters); if (canSkip) { @@ -594,12 +573,8 @@ export class VectorLayer extends AbstractLayer { } _getSourceFeatureCollection() { - if (this._source.isInjectedData()) { - return this.getInjectedData(); - } else { - const sourceDataRequest = this.getSourceDataRequest(); - return sourceDataRequest ? sourceDataRequest.getData() : null; - } + const sourceDataRequest = this.getSourceDataRequest(); + return sourceDataRequest ? sourceDataRequest.getData() : null; } _syncFeatureCollectionWithMb(mbMap) { diff --git a/x-pack/legacy/plugins/ml/common/types/modules.ts b/x-pack/legacy/plugins/ml/common/types/modules.ts index 18879304d7c7f..9eb77d2140323 100644 --- a/x-pack/legacy/plugins/ml/common/types/modules.ts +++ b/x-pack/legacy/plugins/ml/common/types/modules.ts @@ -80,3 +80,5 @@ export interface DataRecognizerConfigResponse { dashboard: KibanaObjectResponse; }; } + +export type JobOverride = Partial; diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/boolean_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/boolean_content.tsx index ea5b1e81b8bf5..d1edf8950d812 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/boolean_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/boolean_content.tsx @@ -28,9 +28,6 @@ function getPercentLabel(valueCount: number, totalCount: number): string { export const BooleanContent: FC = ({ config }) => { const { stats } = config; - if (stats === undefined) { - return null; - } const { count, sampleCount, trueCount, falseCount } = stats; const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx index 24dbb4167c77a..7e20df0444841 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/date_content.tsx @@ -19,9 +19,6 @@ const TIME_FORMAT = 'MMM D YYYY, HH:mm:ss.SSS'; export const DateContent: FC = ({ config }) => { const { stats } = config; - if (stats === undefined) { - return null; - } const { count, sampleCount, earliest, latest } = stats; const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/document_count_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/document_count_content.tsx index a50f49df2fcd6..e9297796c97c1 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/document_count_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/document_count_content.tsx @@ -17,9 +17,6 @@ const CHART_HEIGHT = 350; export const DocumentCountContent: FC = ({ config }) => { const { stats } = config; - if (stats === undefined) { - return null; - } const { documentCounts, timeRangeEarliest, timeRangeLatest } = stats; diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/geo_point_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/geo_point_content.tsx index faf39d2e42373..df8c0a2f09ebb 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/geo_point_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/geo_point_content.tsx @@ -36,9 +36,6 @@ export const GeoPointContent: FC = ({ config }) => { // } const { stats } = config; - if (stats === undefined) { - return null; - } const { count, sampleCount, cardinality, examples } = stats; const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/ip_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/ip_content.tsx index eeffd1e87153c..e27808ceb9a27 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/ip_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/ip_content.tsx @@ -18,9 +18,6 @@ import { TopValues } from '../top_values'; export const IpContent: FC = ({ config }) => { const { stats, fieldFormat } = config; - if (stats === undefined) { - return null; - } const { count, sampleCount, cardinality } = stats; const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/keyword_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/keyword_content.tsx index 92b3848a92511..2fde41cf5d018 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/keyword_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/keyword_content.tsx @@ -18,9 +18,6 @@ import { TopValues } from '../top_values'; export const KeywordContent: FC = ({ config }) => { const { stats, fieldFormat } = config; - if (stats === undefined) { - return null; - } const { count, sampleCount, cardinality } = stats; const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx index b756818c775a4..8fdf826de25f4 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/number_content.tsx @@ -34,9 +34,6 @@ const DEFAULT_TOP_VALUES_THRESHOLD = 100; export const NumberContent: FC = ({ config }) => { const { stats, fieldFormat } = config; - if (stats === undefined) { - return null; - } useEffect(() => { const chartData = buildChartDataFromStats(stats, METRIC_DISTRIBUTION_CHART_WIDTH); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/other_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/other_content.tsx index b2728168425e8..92f389ff88a73 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/other_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/other_content.tsx @@ -16,9 +16,6 @@ import { ExamplesList } from '../examples_list'; export const OtherContent: FC = ({ config }) => { const { stats, type, aggregatable } = config; - if (stats === undefined) { - return null; - } const { count, sampleCount, cardinality, examples } = stats; const docsPercent = roundToDecimalPlace((count / sampleCount) * 100); diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/text_content.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/text_content.tsx index 81fff60960a8d..e0e3ea3ff4888 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/text_content.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/content_types/text_content.tsx @@ -15,9 +15,6 @@ import { ExamplesList } from '../examples_list'; export const TextContent: FC = ({ config }) => { const { stats } = config; - if (stats === undefined) { - return null; - } const { examples } = stats; const numExamples = examples.length; diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/field_data_card.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/field_data_card.tsx index a3641a143faa0..d162d166e8f6b 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/field_data_card.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/components/field_data_card/field_data_card.tsx @@ -30,7 +30,11 @@ export interface FieldDataCardProps { } export const FieldDataCard: FC = ({ config }) => { - const { fieldName, loading, type, existsInDocs } = config; + const { fieldName, loading, type, existsInDocs, stats } = config; + + if (stats === undefined) { + return null; + } function getCardContent() { if (existsInDocs === false) { diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js index d7b2b5ddf18ea..6142ddc78bd2f 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/start_datafeed_modal/time_range_selector/time_range_selector.js @@ -198,7 +198,7 @@ function TabStack({ title, items, switchState, switchFunc }) { return (
  • - switchFunc(item.index)}>{item.label} + switchFunc(item.index)} onKeyUp={() => {}} >{item.label} {(item.body !== undefined) &&
    {item.body} diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/edit_job.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/edit_job.tsx new file mode 100644 index 0000000000000..7ec8cddfe3ed5 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/edit_job.tsx @@ -0,0 +1,136 @@ +/* + * 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, useEffect, useState } from 'react'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiForm, + EuiFormRow, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { ModuleJobUI } from '../page'; +import { usePartialState } from '../../../../components/custom_hooks'; +import { composeValidators, maxLengthValidator } from '../../../../../common/util/validators'; +import { isJobIdValid } from '../../../../../common/util/job_utils'; +import { JOB_ID_MAX_LENGTH } from '../../../../../common/constants/validation'; +import { JobGroupsInput } from '../../common/components'; +import { JobOverride } from '../../../../../common/types/modules'; + +interface EditJobProps { + job: ModuleJobUI; + jobOverride: JobOverride | undefined; + existingGroupIds: string[]; + onClose: (job: JobOverride | null) => void; +} + +/** + * Edit job flyout for overriding job configuration. + */ +export const EditJob: FC = ({ job, jobOverride, existingGroupIds, onClose }) => { + const [formState, setFormState] = usePartialState({ + jobGroups: (jobOverride && jobOverride.groups) || job.config.groups, + }); + const [validationResult, setValidationResult] = useState>({}); + + const groupValidator = composeValidators( + (value: string) => (isJobIdValid(value) ? null : { pattern: true }), + maxLengthValidator(JOB_ID_MAX_LENGTH) + ); + + const handleValidation = () => { + const jobGroupsValidationResult = formState.jobGroups + .map(group => groupValidator(group)) + .filter(result => result !== null); + + setValidationResult({ + jobGroups: jobGroupsValidationResult, + formValid: jobGroupsValidationResult.length === 0, + }); + }; + + useEffect(() => { + handleValidation(); + }, [formState.jobGroups]); + + const onSave = () => { + const result: JobOverride = { + job_id: job.id, + groups: formState.jobGroups, + }; + onClose(result); + }; + + return ( + onClose(null)}> + + +

    + +

    +
    +
    + + + + + { + setFormState({ + jobGroups: value, + }); + }} + validation={{ + valid: !validationResult.jobGroups || validationResult.jobGroups.length === 0, + message: ( + + ), + }} + /> + + + + + + + + onClose(null)} flush="left"> + + + + + onSave()} fill disabled={!validationResult.formValid}> + + + + + +
    + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/job_item.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/job_item.tsx new file mode 100644 index 0000000000000..ace8409734b74 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/job_item.tsx @@ -0,0 +1,172 @@ +/* + * 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, memo } from 'react'; +import { + EuiBadge, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiLoadingSpinner, + EuiText, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { ModuleJobUI } from '../page'; +import { SETUP_RESULTS_WIDTH } from './module_jobs'; +import { tabColor } from '../../../../../common/util/group_color_utils'; +import { JobOverride } from '../../../../../common/types/modules'; + +interface JobItemProps { + job: ModuleJobUI; + jobPrefix: string; + jobOverride: JobOverride | undefined; + isSaving: boolean; + onEditRequest: (job: ModuleJobUI) => void; +} + +export const JobItem: FC = memo( + ({ job, jobOverride, isSaving, jobPrefix, onEditRequest }) => { + const { + id, + config: { description, groups }, + datafeedResult, + setupResult, + } = job; + + const jobGroups = (jobOverride && jobOverride.groups) || groups; + + return ( + + + + + + {jobPrefix} + {id} + + + + + } + > + onEditRequest(job)} + /> + + + + + + {description} + + + + {jobGroups.map(group => ( + + {group} + + ))} + + + {setupResult && setupResult.error && ( + + {setupResult.error.msg} + + )} + + {datafeedResult && datafeedResult.error && ( + + {datafeedResult.error.msg} + + )} + + + {isSaving && } + {setupResult && datafeedResult && ( + + + + + + + + + + + + + + )} + + + ); + } +); diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/job_settings_form.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/job_settings_form.tsx index 617f6b31e7e53..bab45a7d77a3d 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/job_settings_form.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/components/job_settings_form.tsx @@ -27,9 +27,8 @@ import { patternValidator, } from '../../../../../common/util/validators'; import { JOB_ID_MAX_LENGTH } from '../../../../../common/constants/validation'; -import { isJobIdValid } from '../../../../../common/util/job_utils'; import { usePartialState } from '../../../../components/custom_hooks'; -import { JobGroupsInput, TimeRangePicker, TimeRange } from '../../common/components'; +import { TimeRange, TimeRangePicker } from '../../common/components'; export interface JobSettingsFormValues { jobPrefix: string; @@ -37,15 +36,12 @@ export interface JobSettingsFormValues { useFullIndexData: boolean; timeRange: TimeRange; useDedicatedIndex: boolean; - jobGroups: string[]; } interface JobSettingsFormProps { saveState: SAVE_STATE; onSubmit: (values: JobSettingsFormValues) => any; onChange: (values: JobSettingsFormValues) => any; - jobGroups: string[]; - existingGroupIds: string[]; jobs: ModuleJobUI[]; } @@ -53,9 +49,7 @@ export const JobSettingsForm: FC = ({ onSubmit, onChange, saveState, - existingGroupIds, jobs, - jobGroups, }) => { const { from, to } = getTimeFilterRange(); const { currentIndexPattern: indexPattern } = useKibanaContext(); @@ -64,10 +58,6 @@ export const JobSettingsForm: FC = ({ patternValidator(/^([a-z0-9]+[a-z0-9\-_]*)?$/), maxLengthValidator(JOB_ID_MAX_LENGTH - Math.max(...jobs.map(({ id }) => id.length))) ); - const groupValidator = composeValidators( - (value: string) => (isJobIdValid(value) ? null : { pattern: true }), - maxLengthValidator(JOB_ID_MAX_LENGTH) - ); const [formState, setFormState] = usePartialState({ jobPrefix: '', @@ -78,7 +68,6 @@ export const JobSettingsForm: FC = ({ end: to, }, useDedicatedIndex: false, - jobGroups: [] as string[], }); const [validationResult, setValidationResult] = useState>({}); @@ -90,29 +79,21 @@ export const JobSettingsForm: FC = ({ const handleValidation = () => { const jobPrefixValidationResult = jobPrefixValidator(formState.jobPrefix); - const jobGroupsValidationResult = formState.jobGroups - .map(group => groupValidator(group)) - .filter(result => result !== null); setValidationResult({ jobPrefix: jobPrefixValidationResult, - jobGroups: jobGroupsValidationResult, - formValid: !jobPrefixValidationResult && jobGroupsValidationResult.length === 0, + formValid: !jobPrefixValidationResult, }); }; useEffect(() => { handleValidation(); - }, [formState.jobPrefix, formState.jobGroups]); + }, [formState.jobPrefix]); useEffect(() => { onChange(formState); }, [formState]); - useEffect(() => { - setFormState({ jobGroups }); - }, [jobGroups]); - return ( <> @@ -174,24 +155,6 @@ export const JobSettingsForm: FC = ({ /> - { - setFormState({ - jobGroups: value, - }); - }} - validation={{ - valid: !validationResult.jobGroups || validationResult.jobGroups.length === 0, - message: ( - - ), - }} - /> void; } -const SETUP_RESULTS_WIDTH = '200px'; +export const SETUP_RESULTS_WIDTH = '200px'; -export const ModuleJobs: FC = ({ jobs, jobPrefix, saveState }) => { +export const ModuleJobs: FC = ({ + jobs, + jobPrefix, + jobOverrides, + saveState, + existingGroupIds, + onJobOverridesChange, +}) => { const isSaving = saveState === SAVE_STATE.SAVING; + + const [jobToEdit, setJobToEdit] = useState(null); + + const onFlyoutClose = (result: JobOverride | null) => { + setJobToEdit(null); + + if (result === null) { + return; + } + + onJobOverridesChange(result); + }; + + const getJobOverride = (job: ModuleJobUI): JobOverride | undefined => { + return jobOverrides[job.id]; + }; + + const editJobFlyout = + jobToEdit !== null ? ( + + ) : null; + return ( <> @@ -70,109 +107,26 @@ export const ModuleJobs: FC = ({ jobs, jobPrefix, saveState }) )}
      - {jobs.map(({ id, config: { description }, setupResult, datafeedResult }, i) => ( -
    • - + {jobs.map((job, i) => ( +
    • + - - {jobPrefix} - {id} - - - - {description} - - - {setupResult && setupResult.error && ( - - {setupResult.error.msg} - - )} - - {datafeedResult && datafeedResult.error && ( - - {datafeedResult.error.msg} - - )} - - - {isSaving && } - {setupResult && datafeedResult && ( - - - - - - - - - - - - - - )} + setJobToEdit(job)} + /> + {i < jobs.length - 1 && }
    • ))}
    + + {editJobFlyout} ); }; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/page.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/page.tsx index 2c7600dcb99b2..f9a5230ef17d9 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/page.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/recognize/page.tsx @@ -21,12 +21,13 @@ import { EuiPanel, } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; -import { merge, flatten } from 'lodash'; +import { merge } from 'lodash'; import { ml } from '../../../services/ml_api_service'; import { useKibanaContext } from '../../../contexts/kibana'; import { DatafeedResponse, DataRecognizerConfigResponse, + JobOverride, JobResponse, KibanaObject, KibanaObjectResponse, @@ -40,6 +41,7 @@ import { ModuleJobs } from './components/module_jobs'; import { checkForSavedObjects } from './resolvers'; import { JobSettingsForm, JobSettingsFormValues } from './components/job_settings_form'; import { TimeRange } from '../common/components'; +import { JobId } from '../common/job_creator/configs'; export interface ModuleJobUI extends ModuleJob { datafeedResult?: DatafeedResponse; @@ -57,6 +59,8 @@ interface PageProps { existingGroupIds: string[]; } +export type JobOverrides = Record; + export enum SAVE_STATE { NOT_SAVED, SAVING, @@ -69,7 +73,7 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { // #region State const [jobPrefix, setJobPrefix] = useState(''); const [jobs, setJobs] = useState([]); - const [jobGroups, setJobGroups] = useState([]); + const [jobOverrides, setJobOverrides] = useState({}); const [kibanaObjects, setKibanaObjects] = useState({}); const [saveState, setSaveState] = useState(SAVE_STATE.NOT_SAVED); const [resultsUrl, setResultsUrl] = useState(''); @@ -93,6 +97,9 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const displayQueryWarning = savedSearch.id !== undefined; const tempQuery = savedSearch.id === undefined ? undefined : combinedQuery; + /** + * Loads recognizer module configuration. + */ const loadModule = async () => { try { const response: Module = await ml.getDataRecognizerModule({ moduleId }); @@ -101,9 +108,6 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const kibanaObjectsResult = await checkForSavedObjects(response.kibana as KibanaObjects); setKibanaObjects(kibanaObjectsResult); - setJobGroups([ - ...new Set(flatten(response.jobs.map(({ config: { groups = [] } }) => groups))), - ]); setSaveState(SAVE_STATE.NOT_SAVED); } catch (e) { // eslint-disable-next-line no-console @@ -134,11 +138,13 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { loadModule(); }, []); + /** + * Sets up recognizer module configuration. + */ const save = async (formValues: JobSettingsFormValues) => { setSaveState(SAVE_STATE.SAVING); const { jobPrefix: resultJobPrefix, - jobGroups: resultJobGroups, startDatafeedAfterSave, useDedicatedIndex, useFullIndexData, @@ -148,14 +154,17 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { const resultTimeRange = await getTimeRange(useFullIndexData, timeRange); try { + let jobOverridesPayload: JobOverride[] | null = Object.values(jobOverrides); + jobOverridesPayload = jobOverridesPayload.length > 0 ? jobOverridesPayload : null; + const response: DataRecognizerConfigResponse = await ml.setupDataRecognizerConfig({ moduleId, prefix: resultJobPrefix, - groups: resultJobGroups, query: tempQuery, indexPatternName: indexPattern.title, useDedicatedIndex, startDatafeed: startDatafeedAfterSave, + ...(jobOverridesPayload !== null ? { jobOverrides: jobOverridesPayload } : {}), ...resultTimeRange, }); const { datafeeds: datafeedsResponse, jobs: jobsResponse, kibana: kibanaResponse } = response; @@ -208,6 +217,13 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { } }; + const onJobOverridesChange = (job: JobOverride) => { + setJobOverrides({ + ...jobOverrides, + [job.job_id as string]: job, + }); + }; + const isFormVisible = [SAVE_STATE.NOT_SAVED, SAVE_STATE.SAVING].includes(saveState); return ( @@ -271,10 +287,8 @@ export const Page: FC = ({ moduleId, existingGroupIds }) => { onChange={formValues => { setJobPrefix(formValues.jobPrefix); }} - existingGroupIds={existingGroupIds} saveState={saveState} jobs={jobs} - jobGroups={jobGroups} /> )} = ({ moduleId, existingGroupIds }) => { - + {Object.keys(kibanaObjects).length > 0 && ( <> diff --git a/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.js b/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.js index c6a60a9eff7da..94c79fe470236 100644 --- a/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.js +++ b/x-pack/legacy/plugins/ml/public/services/ml_api_service/index.js @@ -292,7 +292,8 @@ export const ml = { 'useDedicatedIndex', 'startDatafeed', 'start', - 'end' + 'end', + 'jobOverrides', ]); return http({ diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/__tests__/data_recognizer.js b/x-pack/legacy/plugins/ml/server/models/data_recognizer/__tests__/data_recognizer.js index 2ed0f9b2db734..af5039fafdc60 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/__tests__/data_recognizer.js +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/__tests__/data_recognizer.js @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ - - import expect from '@kbn/expect'; import { DataRecognizer } from '../data_recognizer'; @@ -36,10 +34,68 @@ describe('ML - data recognizer', () => { expect(ids.join()).to.equal(moduleIds.join()); }); - it('getModule - load a single module', async () => { const module = await dr.getModule(moduleIds[0]); expect(module.id).to.equal(moduleIds[0]); }); + describe('jobOverrides', () => { + it('should apply job overrides correctly', () => { + // arrange + const prefix = 'pre-'; + const testJobId = 'test-job'; + const moduleConfig = { + jobs: [ + { + id: `${prefix}${testJobId}`, + config: { + groups: ['nginx'], + analysis_config: { + bucket_span: '1h' + }, + analysis_limits: { + model_memory_limit: '256mb', + influencers: [ + 'region' + ] + }, + calendars: ['calendar-1'], + } + }, + ], + }; + const jobOverrides = [ + { + analysis_limits: { + model_memory_limit: '512mb', + influencers: [], + } + }, + { + job_id: testJobId, + groups: [], + }, + ]; + // act + dr.applyJobConfigOverrides(moduleConfig, jobOverrides, prefix); + // assert + expect(moduleConfig.jobs).to.eql([ + { + config: { + analysis_config: { + bucket_span: '1h' + }, + analysis_limits: { + model_memory_limit: '512mb', + influencers: [], + }, + groups: [], + calendars: ['calendar-1'], + }, + id: 'pre-test-job' + } + ]); + }); + }); }); + diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.js b/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.js index 0b0d2c5adfea0..b5c897a5a3cc9 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.js +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/data_recognizer.js @@ -811,45 +811,78 @@ export class DataRecognizer { } applyJobConfigOverrides(moduleConfig, jobOverrides, jobPrefix = '') { - if(jobOverrides !== undefined && jobOverrides !== null) { - if (typeof jobOverrides !== 'object') { - throw Boom.badRequest( - `Incompatible jobOverrides type (${typeof jobOverrides}). It needs to be an object or array of objects.` - ); + if (jobOverrides === undefined || jobOverrides === null) { + return; + } + + if (typeof jobOverrides !== 'object') { + throw Boom.badRequest( + `Incompatible jobOverrides type (${typeof jobOverrides}). It needs to be an object or array of objects.` + ); + } + + // jobOverrides could be a single object or an array of objects. + // if single, convert to an array + const overrides = Array.isArray(jobOverrides) ? jobOverrides : [jobOverrides]; + const { jobs } = moduleConfig; + + // separate all the overrides. + // the overrides which don't contain a job id will be applied to all jobs in the module + const generalOverrides = []; + const jobSpecificOverrides = []; + + overrides.forEach(override => { + if (override.job_id === undefined) { + generalOverrides.push(override); + } else { + jobSpecificOverrides.push(override); } + }); - // jobOverrides could be a single object or an array of objects. - // if single, convert to an array - const overrides = Array.isArray(jobOverrides) ? jobOverrides : [jobOverrides]; - const { jobs } = moduleConfig; + function processArrayValues(source, update) { + if (typeof source !== 'object' || typeof update !== 'object') { + return; + } - // separate all the overrides. - // the overrides which don't contain a job id will be applied to all jobs in the module - const generalOverrides = []; - const jobSpecificOverrides = []; - overrides.forEach(o => { - if (o.job_id === undefined) { - generalOverrides.push(o); + Object.keys(source).forEach(key => { + const sourceValue = source[key]; + const updateValue = update[key]; + + if ( + typeof sourceValue !== 'object' || + sourceValue === null || + typeof updateValue !== 'object' || + updateValue === null + ) { + return; + } + + if (Array.isArray(sourceValue) && Array.isArray(updateValue)) { + source[key] = updateValue; } else { - jobSpecificOverrides.push(o); + processArrayValues(sourceValue, updateValue); } }); + } - generalOverrides.forEach(o => { - jobs.forEach(({ config }) => merge(config, o)); + generalOverrides.forEach(generalOverride => { + jobs.forEach(job => { + merge(job.config, generalOverride); + processArrayValues(job.config, generalOverride); }); + }); - jobSpecificOverrides.forEach(o => { - // for each override, find the relevant job. - // note, the job id already has the prefix prepended to it - const job = jobs.find(j => j.id === `${jobPrefix}${o.job_id}`); - if (job !== undefined) { - // delete the job_id in the override as this shouldn't be overridden - delete o.job_id; - merge(job.config, o); - } - }); - } + jobSpecificOverrides.forEach(jobSpecificOverride => { + // for each override, find the relevant job. + // note, the job id already has the prefix prepended to it + const job = jobs.find(j => j.id === `${jobPrefix}${jobSpecificOverride.job_id}`); + if (job !== undefined) { + // delete the job_id in the override as this shouldn't be overridden + delete jobSpecificOverride.job_id; + merge(job.config, jobSpecificOverride); + processArrayValues(job.config, jobSpecificOverride); + } + }); } applyDatafeedConfigOverrides(moduleConfig, datafeedOverrides, jobPrefix = '') { diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js index 9d5ebd274ea9e..5443d6cbee6b5 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js @@ -35,6 +35,7 @@ export class ChartTarget extends React.Component { componentWillUnmount() { this.shutdownChart(); window.removeEventListener('resize', this._handleResize); + this.componentUnmounted = true; } filterByShow(seriesToShow) { @@ -62,7 +63,6 @@ export class ChartTarget extends React.Component { componentDidMount() { this.renderChart(); - window.addEventListener('resize', this._handleResize, false); } componentDidUpdate() { @@ -94,6 +94,9 @@ export class ChartTarget extends React.Component { const data = this.filterData(series, this.props.seriesToShow); this.plot = $.plot(target, data, await this.getOptions()); + if (this.componentUnmounted || !this.plot) { + return; + } this._handleResize = () => { if (!this.plot) { return; } @@ -110,6 +113,8 @@ export class ChartTarget extends React.Component { } }; + window.addEventListener('resize', this._handleResize, false); + this.handleMouseLeave = () => { eventBus.trigger('thorPlotLeave', []); }; diff --git a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap index cda5a9ffe657c..ea5ff3876245c 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/metricbeat_migration/flyout/__snapshots__/flyout.test.js.snap @@ -1298,7 +1298,7 @@ exports[`Flyout kibana part two should show instructions to migrate to metricbea "link": diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap index a6702a4ff6ed5..2a17a2aae8497 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap @@ -10,7 +10,6 @@ exports[`PointToolTipContent renders correctly against snapshot 1`] = ` Object { "_propertyKey": "host.name", "_rawValue": "testPropValue", - "getESFilters": [Function], }, ] } diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx index 3f563550428f8..0bbcd4a7dcbdb 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx @@ -9,13 +9,17 @@ import toJson from 'enzyme-to-json'; import * as React from 'react'; import { LineToolTipContent } from './line_tool_tip_content'; import { FeatureProperty } from '../types'; +import { SUM_OF_DESTINATION_BYTES, SUM_OF_SOURCE_BYTES } from '../map_config'; describe('LineToolTipContent', () => { const mockFeatureProps: FeatureProperty[] = [ { - _propertyKey: 'host.name', + _propertyKey: SUM_OF_DESTINATION_BYTES, + _rawValue: 'testPropValue', + }, + { + _propertyKey: SUM_OF_SOURCE_BYTES, _rawValue: 'testPropValue', - getESFilters: () => new Promise(resolve => setTimeout(resolve)), }, ]; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx index 5103c37c86c23..7cdf3a545a2d6 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx @@ -28,8 +28,11 @@ interface LineToolTipContentProps { export const LineToolTipContent = React.memo( ({ contextId, featureProps }) => { - const lineProps = featureProps.reduce>( - (acc, f) => ({ ...acc, ...{ [f._propertyKey]: f._rawValue } }), + const lineProps = featureProps.reduce>( + (acc, f) => ({ + ...acc, + ...{ [f._propertyKey]: Array.isArray(f._rawValue) ? f._rawValue : [f._rawValue] }, + }), {} ); @@ -44,9 +47,9 @@ export const LineToolTipContent = React.memo( diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx index dca68fe7ab967..567f091e78cb5 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { shallow } from 'enzyme'; +import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import { FeatureProperty } from '../types'; @@ -24,7 +24,13 @@ describe('PointToolTipContent', () => { { _propertyKey: 'host.name', _rawValue: 'testPropValue', - getESFilters: () => new Promise(resolve => setTimeout(resolve)), + }, + ]; + + const mockFeaturePropsArrayValue: FeatureProperty[] = [ + { + _propertyKey: 'host.name', + _rawValue: ['testPropValue1', 'testPropValue2'], }, ]; @@ -43,6 +49,32 @@ describe('PointToolTipContent', () => { expect(toJson(wrapper)).toMatchSnapshot(); }); + test('renders array filter correctly', () => { + const closeTooltip = jest.fn(); + + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="add-to-kql-host.name"]').prop('filter')).toEqual({ + meta: { + alias: null, + disabled: false, + key: 'host.name', + negate: false, + params: { query: 'testPropValue1' }, + type: 'phrase', + value: 'testPropValue1', + }, + query: { match: { 'host.name': { query: 'testPropValue1', type: 'phrase' } } }, + }); + }); + describe('#getRenderedFieldValue', () => { test('it returns empty tag if value is empty', () => { expect(getRenderedFieldValue('host.name', '')).toStrictEqual(getEmptyStringTag()); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.tsx index cd286d82c1bf1..c747c44fd8cca 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.tsx @@ -29,7 +29,7 @@ export const PointToolTipContent = React.memo( title: sourceDestinationFieldMappings[key], description: ( diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts index 3a13753ba25e4..b3d930a13ca35 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/types.ts @@ -51,8 +51,7 @@ export interface LoadFeatureProps { export interface FeatureProperty { _propertyKey: string; - _rawValue: string; - getESFilters(): Promise; + _rawValue: string | string[]; } export interface FeatureGeometry { diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx index c96d25f0d11f0..ba99a92f66b49 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx @@ -22,7 +22,7 @@ import { } from '../../store/timeline/actions'; import { OpenTimeline } from './open_timeline'; import { OPEN_TIMELINE_CLASS_NAME, queryTimelineById, dispatchUpdateTimeline } from './helpers'; -import { OpenTimelineModal } from './open_timeline_modal/open_timeline_modal'; +import { OpenTimelineModalBody } from './open_timeline_modal/open_timeline_modal_body'; import { DeleteTimelines, EuiSearchBarQuery, @@ -281,7 +281,7 @@ export const StatefulOpenTimelineComponent = React.memo( totalSearchResultsCount={totalCount} /> ) : ( - ({ + useApolloClient: () => ({}), +})); -describe('OpenTimelineModalButton', () => { +describe('OpenTimelineModal', () => { const theme = () => ({ eui: euiDarkVars, darkMode: true }); - test('it renders the expected button text', async () => { + test('it renders the expected modal', async () => { const wrapper = mount( - - - - - - ); - - await wait(); - - wrapper.update(); - - expect( - wrapper - .find('[data-test-subj="open-timeline-button"]') - .first() - .text() - ).toEqual(i18n.OPEN_TIMELINE); - }); - - describe('statefulness', () => { - test('defaults showModal to false', async () => { - const wrapper = mount( - - - - - - - - ); - - await wait(); - - wrapper.update(); - - expect(wrapper.find('div[data-test-subj="open-timeline-modal"].euiModal').length).toEqual(0); - }); - - test('it sets showModal to true when the button is clicked', async () => { - const wrapper = mount( - - - - - - - - ); - - await wait(); - - wrapper - .find('[data-test-subj="open-timeline-button"]') - .first() - .simulate('click'); - - wrapper.update(); - - expect(wrapper.find('div[data-test-subj="open-timeline-modal"].euiModal').length).toEqual(1); - }); - - test('it does NOT render the modal when showModal is false', async () => { - const wrapper = mount( + - + - ); - - await wait(); - - wrapper.update(); - - expect( - wrapper - .find('[data-test-subj="open-timeline-modal"]') - .first() - .exists() - ).toBe(false); - }); - - test('it renders the modal when showModal is true', async () => { - const wrapper = mount( - - - - - - - - ); - - await wait(); - - wrapper.update(); - - wrapper - .find('[data-test-subj="open-timeline-button"]') - .first() - .simulate('click'); - - expect( - wrapper - .find('[data-test-subj="open-timeline-modal"]') - .first() - .exists() - ).toBe(true); - }); - }); - - describe('onToggle prop', () => { - test('it still correctly updates the showModal state if `onToggle` is not provided as a prop', async () => { - const wrapper = mount( - - - - - - - - ); - - await wait(); - - wrapper - .find('[data-test-subj="open-timeline-button"]') - .first() - .simulate('click'); - - wrapper.update(); - - expect(wrapper.find('div[data-test-subj="open-timeline-modal"].euiModal').length).toEqual(1); - }); - - test('it invokes the optional onToggle function provided as a prop when the open timeline button is clicked', async () => { - const onToggle = jest.fn(); - const wrapper = mount( - - - - - - - - ); - - await wait(); + + ); - wrapper - .find('[data-test-subj="open-timeline-button"]') - .first() - .simulate('click'); + await wait(); - wrapper.update(); + wrapper.update(); - expect(onToggle).toBeCalled(); - }); + expect(wrapper.find('div[data-test-subj="open-timeline-modal"].euiModal').length).toEqual(1); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx index e8242237cd2c8..cd89eb8aad6f4 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx @@ -4,80 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonEmpty, EuiModal, EuiOverlayMask } from '@elastic/eui'; -import React, { useCallback, useState } from 'react'; +import { EuiModal, EuiOverlayMask } from '@elastic/eui'; +import React from 'react'; -import { ApolloConsumer } from 'react-apollo'; +import { useApolloClient } from '../../../utils/apollo_context'; import * as i18n from '../translations'; import { StatefulOpenTimeline } from '..'; -export interface OpenTimelineModalButtonProps { - /** - * An optional callback that if specified, will perform arbitrary IO before - * this component updates its internal toggle state. - */ - onToggle?: () => void; +export interface OpenTimelineModalProps { + onClose: () => void; } const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; const OPEN_TIMELINE_MODAL_WIDTH = 1000; // px -/** - * Renders a button that when clicked, displays the `Open Timelines` modal - */ -export const OpenTimelineModalButton = React.memo(({ onToggle }) => { - const [showModal, setShowModal] = useState(false); - - /** shows or hides the `Open Timeline` modal */ - const openModal = useCallback(() => { - if (onToggle != null) { - onToggle(); - } - setShowModal(true); - }, [onToggle]); +export const OpenTimelineModal = React.memo(({ onClose }) => { + const apolloClient = useApolloClient(); - const closeModal = useCallback(() => { - if (onToggle != null) { - onToggle(); - } - setShowModal(false); - }, [onToggle]); + if (!apolloClient) return null; return ( - - {client => ( - <> - - {i18n.OPEN_TIMELINE} - - - {showModal && ( - - - - - - )} - - )} - + + + + + ); }); -OpenTimelineModalButton.displayName = 'OpenTimelineModalButton'; +OpenTimelineModal.displayName = 'OpenTimelineModal'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx similarity index 97% rename from x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal.test.tsx rename to x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx index 3a5bf4be5d72f..4237caf8f3c51 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx @@ -14,7 +14,7 @@ import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timeli import { OpenTimelineResult } from '../types'; import { TimelinesTableProps } from '../timelines_table'; import { mockTimelineResults } from '../../../mock/timeline_results'; -import { OpenTimelineModal } from './open_timeline_modal'; +import { OpenTimelineModalBody } from './open_timeline_modal_body'; import { DEFAULT_SORT_DIRECTION, DEFAULT_SORT_FIELD } from '../constants'; jest.mock('../../../lib/settings/use_kibana_ui_setting'); @@ -32,7 +32,7 @@ describe('OpenTimelineModal', () => { test('it renders the title row', () => { const wrapper = mountWithIntl( - { test('it renders the search row', () => { const wrapper = mountWithIntl( - { test('it renders the timelines table', () => { const wrapper = mountWithIntl( - { test('it shows extended columns and actions when onDeleteSelected and deleteTimelines are specified', () => { const wrapper = mountWithIntl( - { test('it does NOT show extended columns and actions when is onDeleteSelected undefined and deleteTimelines is specified', () => { const wrapper = mountWithIntl( - { test('it does NOT show extended columns and actions when is onDeleteSelected provided and deleteTimelines is undefined', () => { const wrapper = mountWithIntl( - { test('it does NOT show extended columns and actions when both onDeleteSelected and deleteTimelines are undefined', () => { const wrapper = mountWithIntl( - ( +export const OpenTimelineModalBody = pure( ({ deleteTimelines, defaultPageSize, @@ -91,4 +91,4 @@ export const OpenTimelineModal = pure( ) ); -OpenTimelineModal.displayName = 'OpenTimelineModal'; +OpenTimelineModalBody.displayName = 'OpenTimelineModalBody'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx new file mode 100644 index 0000000000000..a5e436c73f93b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx @@ -0,0 +1,71 @@ +/* + * 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 euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import { mount } from 'enzyme'; +import * as React from 'react'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { ThemeProvider } from 'styled-components'; + +import { wait } from '../../../lib/helpers'; +import { TestProviderWithoutDragAndDrop } from '../../../mock/test_providers'; +import { mockOpenTimelineQueryResults } from '../../../mock/timeline_results'; +import * as i18n from '../translations'; + +import { OpenTimelineModalButton } from './open_timeline_modal_button'; + +jest.mock('../../../lib/settings/use_kibana_ui_setting'); + +describe('OpenTimelineModalButton', () => { + const theme = () => ({ eui: euiDarkVars, darkMode: true }); + + test('it renders the expected button text', async () => { + const wrapper = mount( + + + + + + ); + + await wait(); + + wrapper.update(); + + expect( + wrapper + .find('[data-test-subj="open-timeline-button"]') + .first() + .text() + ).toEqual(i18n.OPEN_TIMELINE); + }); + + describe('onClick prop', () => { + test('it invokes onClick function provided as a prop when the button is clicked', async () => { + const onClick = jest.fn(); + const wrapper = mount( + + + + + + + + ); + + await wait(); + + wrapper + .find('[data-test-subj="open-timeline-button"]') + .first() + .simulate('click'); + + wrapper.update(); + + expect(onClick).toBeCalled(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.tsx new file mode 100644 index 0000000000000..373868a769d22 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.tsx @@ -0,0 +1,28 @@ +/* + * 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 { EuiButtonEmpty } from '@elastic/eui'; +import React from 'react'; + +import * as i18n from '../translations'; + +export interface OpenTimelineModalButtonProps { + onClick: () => void; +} + +export const OpenTimelineModalButton = React.memo(({ onClick }) => ( + + {i18n.OPEN_TIMELINE} + +)); + +OpenTimelineModalButton.displayName = 'OpenTimelineModalButton'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.tsx index 898990c4497f7..d59d4ccd60c60 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.tsx @@ -39,7 +39,7 @@ interface KpiNetworkProps { export const fieldTitleChartMapping: Readonly = [ { key: 'UniqueIps', - index: 4, + index: 2, fields: [ { key: 'uniqueSourcePrivateIps', @@ -92,7 +92,7 @@ const fieldTitleMatrixMapping: Readonly = [ }, { key: 'uniqueFlowId', - index: 2, + index: 3, fields: [ { key: 'uniqueFlowId', @@ -103,7 +103,7 @@ const fieldTitleMatrixMapping: Readonly = [ }, { key: 'tlsHandshakes', - index: 3, + index: 4, fields: [ { key: 'tlsHandshakes', diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/mock.ts b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/mock.ts index e06bb1477bc7f..38d73d5a5895b 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/mock.ts @@ -223,7 +223,7 @@ export const mockEnableChartsData = { from: 1560578400000, grow: 2, id: 'statItem', - index: 4, + index: 2, statKey: 'UniqueIps', to: 1560837600000, narrowDateRange: mockNarrowDateRange, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx index 111e31479932a..40ba16c0c128a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx @@ -113,6 +113,7 @@ export const Properties = React.memo( }) => { const [showActions, setShowActions] = useState(false); const [showNotes, setShowNotes] = useState(false); + const [showTimelineModal, setShowTimelineModal] = useState(false); const onButtonClick = useCallback(() => { setShowActions(!showActions); @@ -126,6 +127,15 @@ export const Properties = React.memo( setShowActions(false); }, []); + const onOpenTimelineModal = useCallback(() => { + onClosePopover(); + setShowTimelineModal(true); + }, []); + + const onCloseTimelineModal = useCallback(() => { + setShowTimelineModal(false); + }, []); + const datePickerWidth = width - rightGutter - @@ -173,11 +183,14 @@ export const Properties = React.memo( noteIds={noteIds} onButtonClick={onButtonClick} onClosePopover={onClosePopover} + onCloseTimelineModal={onCloseTimelineModal} + onOpenTimelineModal={onOpenTimelineModal} onToggleShowNotes={onToggleShowNotes} showActions={showActions} showDescription={width < showDescriptionThreshold} showNotes={showNotes} showNotesFromWidth={width < showNotesThreshold} + showTimelineModal={showTimelineModal} showUsersView={title.length > 0} timelineId={timelineId} updateDescription={updateDescription} diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx index 2eef253d7be7c..4027682282dcd 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/properties_right.tsx @@ -15,7 +15,8 @@ import { EuiAvatar, } from '@elastic/eui'; import { NewTimeline, Description, NotesButton } from './helpers'; -import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal'; +import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal/open_timeline_modal_button'; +import { OpenTimelineModal } from '../../open_timeline/open_timeline_modal'; import { InspectButton } from '../../inspect'; import * as i18n from './translations'; @@ -75,6 +76,9 @@ interface Props { getNotesByIds: (noteIds: string[]) => Note[]; noteIds: string[]; onToggleShowNotes: () => void; + onCloseTimelineModal: () => void; + onOpenTimelineModal: () => void; + showTimelineModal: boolean; updateNote: UpdateNote; } @@ -98,6 +102,9 @@ export const PropertiesRight = React.memo( noteIds, onToggleShowNotes, updateNote, + showTimelineModal, + onCloseTimelineModal, + onOpenTimelineModal, }) => ( @@ -125,7 +132,7 @@ export const PropertiesRight = React.memo( - + @@ -186,6 +193,8 @@ export const PropertiesRight = React.memo( )) : null} + + {showTimelineModal ? : null} ) ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.test.ts new file mode 100644 index 0000000000000..6c95e82485e45 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.test.ts @@ -0,0 +1,150 @@ +/* + * 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 { buildEventsSearchQuery } from './build_events_query'; + +describe('create_signals', () => { + test('it builds a now-5m up to today filter', () => { + const query = buildEventsSearchQuery({ + index: ['auditbeat-*'], + from: 'now-5m', + to: 'today', + filter: {}, + size: 100, + searchAfterSortId: undefined, + }); + expect(query).toEqual({ + allowNoIndices: true, + index: ['auditbeat-*'], + size: 100, + ignoreUnavailable: true, + body: { + query: { + bool: { + filter: [ + {}, + { + bool: { + filter: [ + { + bool: { + should: [ + { + range: { + '@timestamp': { + gte: 'now-5m', + }, + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + range: { + '@timestamp': { + lte: 'today', + }, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + { + match_all: {}, + }, + ], + }, + }, + track_total_hits: true, + sort: [ + { + '@timestamp': { + order: 'asc', + }, + }, + ], + }, + }); + }); + test('if searchAfterSortId is an empty string it should not be included', () => { + const query = buildEventsSearchQuery({ + index: ['auditbeat-*'], + from: 'now-5m', + to: 'today', + filter: {}, + size: 100, + searchAfterSortId: '', + }); + expect(query).toEqual({ + allowNoIndices: true, + index: ['auditbeat-*'], + size: 100, + ignoreUnavailable: true, + body: { + query: { + bool: { + filter: [ + {}, + { + bool: { + filter: [ + { + bool: { + should: [ + { + range: { + '@timestamp': { + gte: 'now-5m', + }, + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + range: { + '@timestamp': { + lte: 'today', + }, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + { + match_all: {}, + }, + ], + }, + }, + track_total_hits: true, + sort: [ + { + '@timestamp': { + order: 'asc', + }, + }, + ], + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.ts index aa221e8f7fb2b..d2fb7c21f66f5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/build_events_query.ts @@ -4,23 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -interface BuildEventsScrollQuery { +interface BuildEventsSearchQuery { index: string[]; from: string; to: string; filter: unknown; size: number; - scroll: string; + searchAfterSortId?: string; } -export const buildEventsScrollQuery = ({ +export const buildEventsSearchQuery = ({ index, from, to, filter, size, - scroll, -}: BuildEventsScrollQuery) => { + searchAfterSortId, +}: BuildEventsSearchQuery) => { const filterWithTime = [ filter, { @@ -58,10 +58,9 @@ export const buildEventsScrollQuery = ({ }, }, ]; - return { + const searchQuery = { allowNoIndices: true, index, - scroll, size, ignoreUnavailable: true, body: { @@ -76,7 +75,23 @@ export const buildEventsScrollQuery = ({ }, }, track_total_hits: true, - sort: ['_doc'], + sort: [ + { + '@timestamp': { + order: 'asc', + }, + }, + ], }, }; + if (searchAfterSortId) { + return { + ...searchQuery, + body: { + ...searchQuery.body, + search_after: [searchAfterSortId], + }, + }; + } + return searchQuery; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_signals.ts index e5c51d0773679..c1f0b44770ca8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_signals.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/create_signals.ts @@ -27,6 +27,7 @@ export const updateIfIdExists = async ({ maxSignals, name, severity, + size, to, type, references, @@ -49,6 +50,7 @@ export const updateIfIdExists = async ({ maxSignals, name, severity, + size, to, type, references, @@ -78,6 +80,7 @@ export const createSignals = async ({ maxSignals, name, severity, + size, to, type, references, @@ -100,6 +103,7 @@ export const createSignals = async ({ maxSignals, name, severity, + size, to, type, references, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/signals_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/signals_alert_type.ts index 8d1668cc6d958..fc18c1b552198 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/signals_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/signals_alert_type.ts @@ -13,10 +13,10 @@ import { buildEventsReIndex } from './build_events_reindex'; // TODO: Comment this in and use this instead of the reIndex API // once scrolling and other things are done with it. -import { buildEventsScrollQuery } from './build_events_query'; +import { buildEventsSearchQuery } from './build_events_query'; // bulk scroll class -import { scrollAndBulkIndex } from './utils'; +import { searchAfterAndBulkIndex } from './utils'; import { SignalAlertTypeDefinition } from './types'; import { getFilter } from './get_filter'; @@ -42,8 +42,7 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp to: schema.string(), type: schema.string(), references: schema.arrayOf(schema.string(), { defaultValue: [] }), - scrollSize: schema.maybe(schema.number()), - scrollLock: schema.maybe(schema.string()), + size: schema.maybe(schema.number()), }), }, async executor({ services, params }) { @@ -65,12 +64,10 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp severity, to, type, - scrollSize, - scrollLock, + size, } = params; - const scroll = scrollLock ? scrollLock : '1m'; - const size = scrollSize ? scrollSize : 400; + const searchAfterSize = size ? size : 1000; const esFilter = await getFilter({ type, @@ -84,32 +81,12 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp }); // TODO: Turn these options being sent in into a template for the alert type - const noReIndex = buildEventsScrollQuery({ - index, - from, - to, - filter: esFilter, - size, - scroll, - }); - - const reIndex = buildEventsReIndex({ + const noReIndex = buildEventsSearchQuery({ index, from, to, - // TODO: Change this out once we have solved - // https://github.com/elastic/kibana/issues/47002 - signalsIndex: process.env.SIGNALS_INDEX || '.siem-signals-10-01-2019', - severity, - description, - name, - timeDetected: new Date().toISOString(), filter: esFilter, - maxDocs: maxSignals, - ruleRevision: 1, - id, - type, - references, + size: searchAfterSize, }); try { @@ -119,6 +96,24 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp // signals instead of the ReIndex() api if (process.env.USE_REINDEX_API === 'true') { + const reIndex = buildEventsReIndex({ + index, + from, + to, + // TODO: Change this out once we have solved + // https://github.com/elastic/kibana/issues/47002 + signalsIndex: process.env.SIGNALS_INDEX || '.siem-signals-10-01-2019', + severity, + description, + name, + timeDetected: new Date().toISOString(), + filter: esFilter, + maxDocs: maxSignals, + ruleRevision: 1, + id, + type, + references, + }); const result = await services.callCluster('reindex', reIndex); // TODO: Error handling here and writing of any errors that come back from ES by @@ -129,7 +124,7 @@ export const signalsAlertType = ({ logger }: { logger: Logger }): SignalAlertTyp const noReIndexResult = await services.callCluster('search', noReIndex); logger.info(`Total docs to reindex: ${noReIndexResult.hits.total.value}`); - const bulkIndexResult = await scrollAndBulkIndex( + const bulkIndexResult = await searchAfterAndBulkIndex( noReIndexResult, params, services, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts index 4f69c3c8e144b..7db2db5538dbf 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -37,6 +37,7 @@ export interface SignalAlertParams { references: string[]; savedId: string | undefined; severity: string; + size: number | undefined; to: string; type: 'filter' | 'query' | 'saved_query'; } @@ -116,6 +117,12 @@ export interface SignalSource { '@timestamp': string; } +export interface BulkResponse { + took: number; + errors: boolean; + items: unknown[]; +} + export type SignalSearchResponse = SearchResponse; export type SignalSourceHit = SignalSearchResponse['hits']['hits'][0]; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts index 312f484b96e00..08e99de0f2581 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts @@ -3,13 +3,14 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { performance } from 'perf_hooks'; import { SignalHit } from '../../types'; import { Logger } from '../../../../../../../../src/core/server'; import { AlertServices } from '../../../../../alerting/server/types'; -import { SignalSourceHit, SignalSearchResponse, SignalAlertParams } from './types'; +import { SignalSourceHit, SignalSearchResponse, SignalAlertParams, BulkResponse } from './types'; +import { buildEventsSearchQuery } from './build_events_query'; -// format scroll search result for signals index. +// format search_after result for signals index. export const buildBulkBody = (doc: SignalSourceHit, signalParams: SignalAlertParams): SignalHit => { return { ...doc._source, @@ -54,10 +55,15 @@ export const singleBulkIndex = async ( }, buildBulkBody(doc, params), ]); - const firstResult = await service.callCluster('bulk', { - refresh: true, + const time1 = performance.now(); + const firstResult: BulkResponse = await service.callCluster('bulk', { + index: process.env.SIGNALS_INDEX || '.siem-signals-10-01-2019', + refresh: false, body: bulkBody, }); + const time2 = performance.now(); + logger.info(`individual bulk process time took: ${time2 - time1} milliseconds`); + logger.info(`took property says bulk took: ${firstResult.took} milliseconds`); if (firstResult.errors) { logger.error(`[-] bulkResponse had errors: ${JSON.stringify(firstResult.errors, null, 2)}}`); return false; @@ -65,28 +71,35 @@ export const singleBulkIndex = async ( return true; }; -// Given a scroll id, grab the next set of documents -export const singleScroll = async ( - scrollId: string | undefined, - params: SignalAlertParams & { scrollLock?: number }, // TODO: Finish plumbing the scrollLock all the way to the REST endpoint if this algorithm continues to use it. +// utilize search_after for paging results into bulk. +export const singleSearchAfter = async ( + searchAfterSortId: string | undefined, + params: SignalAlertParams, service: AlertServices, logger: Logger ): Promise => { - const scroll = params.scrollLock ? params.scrollLock : '1m'; + if (searchAfterSortId == null) { + throw Error('Attempted to search after with empty sort id'); + } try { - const nextScrollResult = await service.callCluster('scroll', { - scroll, - scrollId, + const searchAfterQuery = buildEventsSearchQuery({ + index: params.index, + from: params.from, + to: params.to, + filter: params.filter, + size: params.size ? params.size : 1000, + searchAfterSortId, }); - return nextScrollResult; + const nextSearchAfterResult = await service.callCluster('search', searchAfterQuery); + return nextSearchAfterResult; } catch (exc) { - logger.error(`[-] nextScroll threw an error ${exc}`); + logger.error(`[-] nextSearchAfter threw an error ${exc}`); throw exc; } }; -// scroll through documents and re-index using bulk endpoint. -export const scrollAndBulkIndex = async ( +// search_after through documents and re-index using bulk endpoint. +export const searchAfterAndBulkIndex = async ( someResult: SignalSearchResponse, params: SignalAlertParams, service: AlertServices, @@ -98,22 +111,51 @@ export const scrollAndBulkIndex = async ( logger.warn('First bulk index was unsuccessful'); return false; } - let newScrollId = someResult._scroll_id; - while (true) { + + const totalHits = + typeof someResult.hits.total === 'number' ? someResult.hits.total : someResult.hits.total.value; + let size = someResult.hits.hits.length - 1; + logger.info(`first size: ${size}`); + let sortIds = someResult.hits.hits[0].sort; + if (sortIds == null && totalHits > 0) { + logger.warn('sortIds was empty on first search but expected more '); + return false; + } else if (sortIds == null && totalHits === 0) { + return true; + } + let sortId; + if (sortIds != null) { + sortId = sortIds[0]; + } + while (size < totalHits) { + // utilize track_total_hits instead of true try { - const scrollResult = await singleScroll(newScrollId, params, service, logger); - newScrollId = scrollResult._scroll_id; - if (scrollResult.hits.hits.length === 0) { - logger.info('[+] Finished indexing signals'); - return true; + logger.info(`sortIds: ${sortIds}`); + const searchAfterResult: SignalSearchResponse = await singleSearchAfter( + sortId, + params, + service, + logger + ); + size += searchAfterResult.hits.hits.length - 1; + logger.info(`size: ${size}`); + sortIds = searchAfterResult.hits.hits[0].sort; + if (sortIds == null) { + logger.warn('sortIds was empty search'); + return false; } - const bulkSuccess = await singleBulkIndex(scrollResult, params, service, logger); + sortId = sortIds[0]; + logger.info('next bulk index'); + const bulkSuccess = await singleBulkIndex(searchAfterResult, params, service, logger); + logger.info('finished next bulk index'); if (!bulkSuccess) { logger.error('[-] bulk index failed'); } } catch (exc) { - logger.error('[-] scroll and bulk threw an error'); + logger.error(`[-] search_after and bulk threw an error ${exc}`); return false; } } + logger.info(`[+] completed bulk index of ${totalHits}`); + return true; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_signals_route.ts index a69523c907b0a..f1fc159ffeaee 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/create_signals_route.ts @@ -40,6 +40,7 @@ export const createCreateSignalsRoute: Hapi.ServerRoute = { max_signals: maxSignals, name, severity, + size, to, type, references, @@ -69,6 +70,7 @@ export const createCreateSignalsRoute: Hapi.ServerRoute = { maxSignals, name, severity, + size, to, type, references, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_signals_route.ts index e11f566f9720c..08307ef633ffe 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/update_signals_route.ts @@ -48,6 +48,7 @@ export const createUpdateSignalsRoute: Hapi.ServerRoute = { max_signals: maxSignals, name, severity, + size, to, type, references, @@ -77,6 +78,7 @@ export const createUpdateSignalsRoute: Hapi.ServerRoute = { maxSignals, name, severity, + size, to, type, references, diff --git a/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.test.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.test.ts index 2be7724f4097f..53ed7c26b877f 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.test.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.test.ts @@ -6,7 +6,7 @@ import { cloneDeep, isArray } from 'lodash/fp'; -import { convertSchemaToAssociativeArray, getIndexSchemaDoc } from '.'; +import { convertSchemaToAssociativeArray, getIndexSchemaDoc, getIndexAlias } from '.'; import { auditbeatSchema, filebeatSchema, packetbeatSchema } from './8.0.0'; import { Schema } from './type'; @@ -657,4 +657,17 @@ describe('Schema Beat', () => { ]); }); }); + + describe('getIndexAlias', () => { + test('getIndexAlias handles values with leading wildcard', () => { + const leadingWildcardIndex = '*-auditbeat-*'; + const result = getIndexAlias([leadingWildcardIndex], leadingWildcardIndex); + expect(result).toBe(leadingWildcardIndex); + }); + test('getIndexAlias no match returns "unknown" string', () => { + const index = 'auditbeat-*'; + const result = getIndexAlias([index], 'hello'); + expect(result).toBe('unknown'); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.ts index aaa171c6befd9..a191bd835a7c7 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.ts @@ -77,7 +77,7 @@ const convertFieldsToAssociativeArray = ( : {}; export const getIndexAlias = (defaultIndex: string[], indexName: string): string => { - const found = defaultIndex.find(index => indexName.match(index) != null); + const found = defaultIndex.find(index => `\\${indexName}`.match(`\\${index}`) != null); if (found != null) { return found; } else { diff --git a/x-pack/legacy/plugins/transform/public/app/common/transform.ts b/x-pack/legacy/plugins/transform/public/app/common/transform.ts index 7670ab1f1cca1..481ad3c6d74ff 100644 --- a/x-pack/legacy/plugins/transform/public/app/common/transform.ts +++ b/x-pack/legacy/plugins/transform/public/app/common/transform.ts @@ -95,6 +95,8 @@ export const useRefreshTransformList = ( return () => { subscriptions.map(sub => sub.unsubscribe()); }; + // The effect should only be called once. + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return { diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx index b64260b92d473..7d615bfa521f0 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/kibana_provider.tsx @@ -75,6 +75,8 @@ export const KibanaProvider: FC = ({ savedObjectId, children }) => { useEffect(() => { fetchSavedObject(savedObjectId); + // fetchSavedObject should not be tracked. + // eslint-disable-next-line react-hooks/exhaustive-deps }, [savedObjectId]); return {children}; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts index 1f8f25d33e807..9205d03e6401b 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/source_index_preview/use_source_index_data.ts @@ -131,6 +131,8 @@ export const useSourceIndexData = ( useEffect(() => { getSourceIndexData(); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps }, [indexPattern.title, JSON.stringify(query)]); return { errorMessage, status, tableItems }; }; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx index 9623ff6abeced..6e9505898ced4 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_create/step_create_form.tsx @@ -76,6 +76,8 @@ export const StepCreateForm: SFC = React.memo( useEffect(() => { onChange({ created, started, indexPatternId }); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps }, [created, started, indexPatternId]); const api = useApi(); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx index bbfc6b11d3619..b53e0fca0fee1 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/pivot_preview.tsx @@ -150,7 +150,7 @@ export const PivotPreview: SFC = React.memo(({ aggs, groupBy, if (clearTable) { setTimeout(() => setClearTable(false), 0); } - }); + }, [firstColumnNameChanged, clearTable]); if (firstColumnNameChanged) { return null; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx index 6cde57fc2316d..442ce2011f77e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/step_define_form.tsx @@ -487,6 +487,8 @@ export const StepDefineForm: SFC = React.memo(({ overrides = {}, onChange sourceConfigUpdated, valid, }); + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ }, [ JSON.stringify(pivotAggsArr), JSON.stringify(pivotGroupByArr), @@ -495,6 +497,7 @@ export const StepDefineForm: SFC = React.memo(({ overrides = {}, onChange searchString, searchQuery, valid, + /* eslint-enable react-hooks/exhaustive-deps */ ]); // TODO This should use the actual value of `indices.query.bool.max_clause_count` diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.ts index 17deb4db31990..92e3bdded4f6a 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.ts @@ -91,11 +91,14 @@ export const usePivotPreviewData = ( useEffect(() => { getPreviewData(); + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ }, [ indexPattern.title, JSON.stringify(aggsArr), JSON.stringify(groupByArr), JSON.stringify(query), + /* eslint-enable react-hooks/exhaustive-deps */ ]); return { errorMessage, status, previewData, previewMappings, previewRequest }; diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index ba43e020674ac..962a8905056b6 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -121,6 +121,8 @@ export const StepDetailsForm: SFC = React.memo(({ overrides = {}, onChang } } })(); + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps }, [kibanaContext.initialized]); if (!isKibanaContextInitialized(kibanaContext)) { @@ -169,6 +171,8 @@ export const StepDetailsForm: SFC = React.memo(({ overrides = {}, onChang touched: true, valid, }); + // custom comparison + /* eslint-disable react-hooks/exhaustive-deps */ }, [ continuousModeDateField, continuousModeDelay, @@ -178,6 +182,7 @@ export const StepDetailsForm: SFC = React.memo(({ overrides = {}, onChang transformDescription, destinationIndex, valid, + /* eslint-enable react-hooks/exhaustive-deps */ ]); return ( diff --git a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts index 7a2e6b9e623fb..8e505c7cccc02 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/transform_management/components/transform_list/use_refresh_interval.ts @@ -77,5 +77,7 @@ export const useRefreshInterval = ( refreshIntervalSubscription.unsubscribe(); clearRefreshInterval(); }; + // custom comparison + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // [] as comparator makes sure this only runs once }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_charts.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_charts.test.tsx.snap index d915bcb72e05e..511485e70d0dc 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_charts.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_charts.test.tsx.snap @@ -1,82 +1,81 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`MonitorCharts component renders the component without errors 1`] = ` - - - - - - - + + + + + - - - + } + /> + + `; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/ping_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/ping_list.test.tsx.snap index 11b26bff9ea36..165cfe5c370d1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/ping_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/ping_list.test.tsx.snap @@ -150,7 +150,7 @@ exports[`PingList component renders sorted list without errors 1`] = ` "render": [Function], }, Object { - "align": "left", + "align": "right", "dataType": "number", "field": "monitor.ip", "name": "IP", diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot.test.tsx.snap index d6b96fef9b2ee..ebbd8a4ac56a8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot.test.tsx.snap @@ -4,22 +4,10 @@ exports[`Snapshot component renders without errors 1`] = ` - -

    - -

    -
    + diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot_heading.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot_heading.test.tsx.snap new file mode 100644 index 0000000000000..da2e39c3dc127 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/snapshot_heading.test.tsx.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SnapshotHeading renders custom heading for no down monitors 1`] = ` + +

    + All monitors are up +

    +
    +`; + +exports[`SnapshotHeading renders custom heading for no monitors 1`] = ` + +

    + No monitors found +

    +
    +`; + +exports[`SnapshotHeading renders standard heading for valid counts 1`] = ` + +

    + 3/17 monitors are down +

    +
    +`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot_heading.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot_heading.test.tsx new file mode 100644 index 0000000000000..5ddef3d0aabd1 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot_heading.test.tsx @@ -0,0 +1,26 @@ +/* + * 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 { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import React from 'react'; +import { SnapshotHeading } from '../snapshot_heading'; + +describe('SnapshotHeading', () => { + it('renders custom heading for no down monitors', () => { + const wrapper = shallowWithIntl(); + expect(wrapper).toMatchSnapshot(); + }); + + it('renders standard heading for valid counts', () => { + const wrapper = shallowWithIntl(); + expect(wrapper).toMatchSnapshot(); + }); + + it('renders custom heading for no monitors', () => { + const wrapper = shallowWithIntl(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/snapshot_histogram.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/snapshot_histogram.test.tsx index bad6c5e2a2ba1..db78c063b7ed5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/snapshot_histogram.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/snapshot_histogram.test.tsx @@ -12,6 +12,7 @@ describe('SnapshotHistogram component', () => { const props: SnapshotHistogramProps = { absoluteStartDate: 1548697920000, absoluteEndDate: 1548700920000, + isResponsive: false, }; it('renders the component without errors', () => { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx index a5d2d9cfc27f2..37edd20871245 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx @@ -19,27 +19,16 @@ import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; -import styled from 'styled-components'; import { getColorsMap } from './get_colors_map'; import { getChartDateLabel } from '../../../lib/helper'; import { withUptimeGraphQL, UptimeGraphQLQueryProps } from '../../higher_order'; import { snapshotHistogramQuery } from '../../../queries/snapshot_histogram_query'; import { ChartWrapper } from './chart_wrapper'; import { UptimeSettingsContext } from '../../../contexts'; +import { ResponsiveWrapperProps, withResponsiveWrapper } from '../../higher_order'; import { HistogramResult } from '../../../../common/domain_types'; -const SnapshotHistogramWrapper = styled.div` - margin-left: 120px; - @media (max-width: 950px) { - margin-left: 48px; - } - @media (max-width: 767px) { - margin-left: 12px; - margin-top: 40px; - } -`; - -export interface SnapshotHistogramProps { +interface HistogramProps { /** * The date/time for the start of the timespan. */ @@ -55,13 +44,17 @@ export interface SnapshotHistogramProps { height?: string; } +export type SnapshotHistogramProps = HistogramProps & ResponsiveWrapperProps; + interface SnapshotHistogramQueryResult { queryResult?: HistogramResult; } -type Props = UptimeGraphQLQueryProps & SnapshotHistogramProps; +type Props = UptimeGraphQLQueryProps & + SnapshotHistogramProps & + ResponsiveWrapperProps; -export const SnapshotHistogramComponent = ({ +export const SnapshotHistogramComponent: React.FC = ({ absoluteStartDate, absoluteEndDate, data, @@ -125,7 +118,7 @@ export const SnapshotHistogramComponent = ({ }); const upSpecId = getSpecId(upMonitorsId); return ( - + <>

    - + ); }; export const SnapshotHistogram = withUptimeGraphQL< SnapshotHistogramQueryResult, SnapshotHistogramProps ->(SnapshotHistogramComponent, snapshotHistogramQuery); +>(withResponsiveWrapper(SnapshotHistogramComponent), snapshotHistogramQuery); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/__snapshots__/filter_status_button.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/__snapshots__/filter_status_button.test.tsx.snap index 22eacdb91c0dc..28ac27054b856 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/__snapshots__/filter_status_button.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/__snapshots__/filter_status_button.test.tsx.snap @@ -3,6 +3,7 @@ exports[`FilterStatusButton renders without errors for valid props 1`] = ` { beforeEach(() => { props = { content: 'Up', + dataTestSubj: 'foo', value: 'up', withNext: true, }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_group.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_group.tsx index cfdb27d9c4122..f27514bf76a11 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_group.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_group.tsx @@ -105,6 +105,7 @@ export const FilterGroupComponent = ({ content={i18n.translate('xpack.uptime.filterBar.filterUpLabel', { defaultMessage: 'Up', })} + dataTestSubj="xpack.uptime.filterBar.filterStatusUp" value="up" withNext={true} /> @@ -112,6 +113,7 @@ export const FilterGroupComponent = ({ content={i18n.translate('xpack.uptime.filterBar.filterDownLabel', { defaultMessage: 'Down', })} + dataTestSubj="xpack.uptime.filterBar.filterStatusDown" value="down" withNext={false} /> diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_status_button.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_status_button.tsx index 6f2659ddbc83e..95f4c30337d62 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_status_button.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_status_button.tsx @@ -10,15 +10,22 @@ import { useUrlParams } from '../../../hooks'; export interface FilterStatusButtonProps { content: string; + dataTestSubj: string; value: string; withNext: boolean; } -export const FilterStatusButton = ({ content, value, withNext }: FilterStatusButtonProps) => { +export const FilterStatusButton = ({ + content, + dataTestSubj, + value, + withNext, +}: FilterStatusButtonProps) => { const [getUrlParams, setUrlParams] = useUrlParams(); const { statusFilter: urlValue } = getUrlParams(); return ( { const nextFilter = { statusFilter: urlValue === value ? '' : value, pagination: '' }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_charts.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_charts.tsx index 51c58a871d0e7..7d8e788f49ea0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_charts.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_charts.tsx @@ -48,26 +48,25 @@ export const MonitorChartsComponent = ({ const { absoluteDateRangeStart, absoluteDateRangeEnd } = getUrlParams(); return ( - - - - - - - - - - + + + + + + + + ); } return ( diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap index e53d73fb5a461..c2167993b80b9 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap @@ -88,6 +88,7 @@ exports[`MonitorList component renders a no items message when no data is provid grow={false} > @@ -95,6 +96,7 @@ exports[`MonitorList component renders a no items message when no data is provid grow={false} > @@ -253,6 +255,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` grow={false} > @@ -260,6 +263,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` grow={false} > diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap index cd03c6aad5611..619cce88b309b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list_pagination.test.tsx.snap @@ -88,6 +88,7 @@ exports[`MonitorList component renders a no items message when no data is provid grow={false} > @@ -95,6 +96,7 @@ exports[`MonitorList component renders a no items message when no data is provid grow={false} > @@ -253,6 +255,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` grow={false} > @@ -261,6 +264,7 @@ exports[`MonitorList component renders the monitor list 1`] = ` grow={false} > diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx index cf3a0ef4e37e7..5774453ff67ab 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list.tsx @@ -248,10 +248,18 @@ export const MonitorListComponent = (props: Props) => { - + - + diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/overview_page_link.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/overview_page_link.tsx index 9153b6018ce72..5b0ad2ce58537 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/overview_page_link.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/overview_page_link.tsx @@ -10,13 +10,15 @@ import { i18n } from '@kbn/i18n'; import { useUrlParams } from '../../hooks'; interface OverviewPageLinkProps { - pagination: string; + dataTestSubj: string; direction: string; + pagination: string; } export const OverviewPageLink: FunctionComponent = ({ - pagination, + dataTestSubj, direction, + pagination, }) => { const [, updateUrlParams] = useUrlParams(); const icon = direction === 'prev' ? 'arrowLeft' : 'arrowRight'; @@ -34,6 +36,7 @@ export const OverviewPageLink: FunctionComponent = ({ return !!pagination ? ( { updateUrlParams({ pagination }); }} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list.tsx index 705a757a83eb7..5b280c5aceb87 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/ping_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/ping_list.tsx @@ -151,7 +151,7 @@ export const PingListComponent = ({ render: (location: string) => , }, { - align: 'left', + align: 'right', dataType: 'number', field: 'monitor.ip', name: i18n.translate('xpack.uptime.pingList.ipAddressColumnLabel', { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx index bfc8cb7003868..8d89e53a41a45 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiSpacer, EuiTitle } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiSpacer } from '@elastic/eui'; import React from 'react'; import { get } from 'lodash'; import { DonutChart } from './charts'; @@ -13,6 +12,7 @@ import { Snapshot as SnapshotType } from '../../../common/graphql/types'; import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../higher_order'; import { snapshotQuery } from '../../queries'; import { ChartWrapper } from './charts/chart_wrapper'; +import { SnapshotHeading } from './snapshot_heading'; const SNAPSHOT_CHART_WIDTH = 144; const SNAPSHOT_CHART_HEIGHT = 144; @@ -31,18 +31,10 @@ export const SnapshotComponent = ({ loading, }: UptimeGraphQLQueryProps) => ( - -

    - (data, 'snapshot.counts.down', 0), - total: get(data, 'snapshot.counts.total', 0), - }} - /> -

    -
    + (data, 'snapshot.counts.down', 0)} + total={get(data, 'snapshot.counts.total', 0)} + /> (data, 'snapshot.counts.up', 0)} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot_heading.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot_heading.tsx new file mode 100644 index 0000000000000..85d1294d4b064 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot_heading.tsx @@ -0,0 +1,39 @@ +/* + * 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 { EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +interface Props { + down: number; + total: number; +} + +const getMessage = (down: number, total: number): string => { + if (down === 0 && total > 0) { + return i18n.translate('xpack.uptime.snapshot.zeroDownMessage', { + defaultMessage: 'All monitors are up', + }); + } else if (down === 0 && total === 0) { + return i18n.translate('xpack.uptime.snapshot.noMonitorMessage', { + defaultMessage: 'No monitors found', + }); + } + return i18n.translate('xpack.uptime.snapshot.downCountsMessage', { + defaultMessage: '{down}/{total} monitors are down', + values: { + down, + total, + }, + }); +}; + +export const SnapshotHeading = ({ down, total }: Props) => ( + +

    {getMessage(down, total)}

    +
    +); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx index 9f1e4648b072d..e941c2dad87d2 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx @@ -8,19 +8,16 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import React from 'react'; import { SnapshotHistogram } from './charts'; import { Snapshot } from './snapshot'; -import { UptimeAppColors } from '../../uptime_app'; interface StatusPanelProps { absoluteDateRangeStart: number; absoluteDateRangeEnd: number; - colors: UptimeAppColors; sharedProps: { [key: string]: any }; } export const StatusPanel = ({ absoluteDateRangeStart, absoluteDateRangeEnd, - colors: { danger, success }, sharedProps, }: StatusPanelProps) => ( @@ -32,8 +29,9 @@ export const StatusPanel = ({ diff --git a/x-pack/legacy/plugins/uptime/public/components/higher_order/__tests__/__snapshots__/responsive_wrapper.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/higher_order/__tests__/__snapshots__/responsive_wrapper.test.tsx.snap new file mode 100644 index 0000000000000..65b6d7cc39e55 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/higher_order/__tests__/__snapshots__/responsive_wrapper.test.tsx.snap @@ -0,0 +1,221 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ResponsiveWrapper HOC is not responsive when prop is false 1`] = ` + + + +`; + +exports[`ResponsiveWrapper HOC renders a responsive wrapper 1`] = ` + + + +`; diff --git a/x-pack/legacy/plugins/uptime/public/components/higher_order/__tests__/responsive_wrapper.test.tsx b/x-pack/legacy/plugins/uptime/public/components/higher_order/__tests__/responsive_wrapper.test.tsx new file mode 100644 index 0000000000000..ae265ef1bb0c6 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/higher_order/__tests__/responsive_wrapper.test.tsx @@ -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 React from 'react'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { withResponsiveWrapper } from '../responsive_wrapper'; + +interface Prop { + isResponsive: boolean; +} + +describe('ResponsiveWrapper HOC', () => { + let WrappedByHOC: React.FC; + beforeEach(() => { + WrappedByHOC = withResponsiveWrapper(() =>
    Should be responsive
    ); + }); + + it('renders a responsive wrapper', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('is not responsive when prop is false', () => { + const component = shallowWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/higher_order/index.ts b/x-pack/legacy/plugins/uptime/public/components/higher_order/index.ts index c5b712e0ae674..e0e14456cfc68 100644 --- a/x-pack/legacy/plugins/uptime/public/components/higher_order/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/higher_order/index.ts @@ -5,3 +5,4 @@ */ export { UptimeGraphQLQueryProps, withUptimeGraphQL } from './uptime_graphql_query'; +export { ResponsiveWrapperProps, withResponsiveWrapper } from './responsive_wrapper'; diff --git a/x-pack/legacy/plugins/uptime/public/components/higher_order/responsive_wrapper.tsx b/x-pack/legacy/plugins/uptime/public/components/higher_order/responsive_wrapper.tsx new file mode 100644 index 0000000000000..f383600ab8aa1 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/higher_order/responsive_wrapper.tsx @@ -0,0 +1,41 @@ +/* + * 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 { EuiPanel } from '@elastic/eui'; +import React, { FC } from 'react'; +import styled from 'styled-components'; + +const ResponsiveWrapper = styled.div` + margin-left: 120px; + @media (max-width: 950px) { + margin-left: 48px; + } + @media (max-width: 767px) { + margin-left: 12px; + margin-top: 40px; + } +`; + +export interface ResponsiveWrapperProps { + isResponsive: boolean; +} + +/** + * HOC that wraps a component in either a responsive div or an EuiPanel. + * @param Component The component to wrap. + */ +export const withResponsiveWrapper =

    ( + Component: FC

    +): FC => ({ isResponsive, ...rest }: ResponsiveWrapperProps) => + isResponsive ? ( + + + + ) : ( + + + + ); diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx index 445ebcb5547b8..ded16c3f8eb2f 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx @@ -151,7 +151,6 @@ export const OverviewPage = ({ diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 39db6ff3791df..eb748a5739b34 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -922,21 +922,20 @@ "inputControl.vis.listControl.selectPlaceholder": "選択してください…", "inputControl.vis.listControl.selectTextPlaceholder": "選択してください…", "inputControl.vis.listControl.partialResultsWarningMessage": "リクエストに長くかかり過ぎているため、用語リストが不完全な可能性があります。完全な結果を得るには、kibana.yml の自動完了設定を調整してください。", - "interpreter.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名)", - "interpreter.function.visDimension.error.accessor": "入力された列名は無効です。", - "interpreter.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します", + "visualizations.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名)", + "visualizations.function.visDimension.error.accessor": "入力された列名は無効です。", + "visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します", "interpreter.functions.esaggs.help": "AggConfig 集約を実行します", - "interpreter.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", - "interpreter.functions.kibana.help": "Kibana グローバルコンテキストを取得します", - "interpreter.functions.visualization.help": "シンプルなビジュアライゼーションです", - "interpreter.functions.font.args.alignHelpText": "水平テキスト配置", - "interpreter.functions.font.args.colorHelpText": "文字の色です。", - "interpreter.functions.font.args.italicHelpText": "テキストを斜体にしますか?", - "interpreter.functions.font.args.lHeightHelpText": "ピクセル単位の行の高さです。", - "interpreter.functions.font.args.sizeHelpText": "ピクセル単位のフォントサイズです。", - "interpreter.functions.font.args.underlineHelpText": "テキストに下線を引きますか?", - "interpreter.functions.font.args.weightHelpText": "フォントの重量です。例: {list} または {end}。", - "interpreter.functions.fontHelpText": "フォントスタイルを作成します。", + "expressions_np.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", + "expressions_np.functions.kibana.help": "Kibana グローバルコンテキストを取得します", + "expressions_np.functions.font.args.alignHelpText": "水平テキスト配置", + "expressions_np.functions.font.args.colorHelpText": "文字の色です。", + "expressions_np.functions.font.args.italicHelpText": "テキストを斜体にしますか?", + "expressions_np.functions.font.args.lHeightHelpText": "ピクセル単位の行の高さです。", + "expressions_np.functions.font.args.sizeHelpText": "ピクセル単位のフォントサイズです。", + "expressions_np.functions.font.args.underlineHelpText": "テキストに下線を引きますか?", + "expressions_np.functions.font.args.weightHelpText": "フォントの重量です。例: {list} または {end}。", + "expressions_np.functions.fontHelpText": "フォントスタイルを作成します。", "kbn.advancedSettings.context.defaultSizeText": "コンテキストビューに表示される周りのエントリーの数", "kbn.advancedSettings.context.defaultSizeTitle": "コンテキストサイズ", "kbn.advancedSettings.context.sizeStepText": "コンテキストサイズを増減させる際の最低単位です", @@ -3254,9 +3253,6 @@ "xpack.apm.invalidLicense.licenseManagementLink": "ライセンスを更新", "xpack.apm.invalidLicense.message": "現在ご使用のライセンスが期限切れか有効でなくなったため、APM UI を利用できません。", "xpack.apm.invalidLicense.title": "無効なライセンス", - "xpack.apm.kueryBar.indexPatternMissingWarningMessage": "{apmIndexPatternTitle} というタイトルで利用可能な APM インデックスパターンがありません。クエリバーを使用するには、{setupInstructionsLink} での APM インデックスパターンのインポートを選択してください。", - "xpack.apm.kueryBar.searchPlaceholder": "トランザクションとエラーを検索… (例: {queryExample})", - "xpack.apm.kueryBar.setupInstructionsLinkLabel": "セットアップの手順", "xpack.apm.metadataTable.section.agentLabel": "エージェント", "xpack.apm.metadataTable.section.containerLabel": "コンテナー", "xpack.apm.metadataTable.section.customLabel": "カスタム", @@ -5067,11 +5063,7 @@ "xpack.infra.logs.analysisPage.unavailable.mLDisabledTitle": "分析機能には機械学習が必要です", "xpack.infra.logs.highlights.goToNextHighlightButtonLabel": "次のハイライトにスキップ", "xpack.infra.logs.highlights.goToPreviousHighlightButtonLabel": "前のハイライトにスキップ", - "xpack.infra.logs.index.analysisBetaBadgeLabel": "ベータ", "xpack.infra.logs.index.analysisBetaBadgeTitle": "分析", - "xpack.infra.logs.index.analysisBetaBadgeTooltipContent": "この機能は現在開発中です。他にも機能が追加され、機能によっては変更されるものもあります。", - "xpack.infra.logs.index.analysisTabTitle": "分析", - "xpack.infra.logs.index.documentTitle": "ログ", "xpack.infra.logs.index.settingsTabTitle": "設定", "xpack.infra.logs.index.streamTabTitle": "ストリーム", "xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent": "機械学習ロボットがデータの収集を開始するまでしばらくお待ちください。", @@ -10323,4 +10315,4 @@ "xpack.fileUpload.fileParser.errorReadingFile": "ファイルの読み込み中にエラーが発生しました", "xpack.fileUpload.fileParser.noFileProvided": "エラー、ファイルが提供されていません" } -} \ No newline at end of file +} diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 10ffb2b7d731d..053fe90b81379 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -923,21 +923,20 @@ "inputControl.vis.listControl.selectPlaceholder": "选择......", "inputControl.vis.listControl.selectTextPlaceholder": "选择......", "inputControl.vis.listControl.partialResultsWarningMessage": "字词列表可能不完整,因为请求花费的时间过长。在 Kibana.yml 中调整自动完成设置以获取完整的结果。", - "interpreter.function.visDimension.accessor.help": "数据集中要使用的列(列索引或列名称)", - "interpreter.function.visDimension.error.accessor": "提供的列名称无效", - "interpreter.function.visDimension.help": "生成 visConfig 维度对象", + "visualizations.function.visDimension.accessor.help": "数据集中要使用的列(列索引或列名称)", + "visualizations.function.visDimension.error.accessor": "提供的列名称无效", + "visualizations.function.visDimension.help": "生成 visConfig 维度对象", "interpreter.functions.esaggs.help": "运行 AggConfig 聚合", - "interpreter.functions.kibana_context.help": "更新 kibana 全局上下文", - "interpreter.functions.kibana.help": "获取 kibana 全局上下文", - "interpreter.functions.visualization.help": "简单可视化", - "interpreter.functions.font.args.alignHelpText": "水平文本对齐。", - "interpreter.functions.font.args.colorHelpText": "文本颜色。", - "interpreter.functions.font.args.italicHelpText": "使文本变为斜体?", - "interpreter.functions.font.args.lHeightHelpText": "行高(像素)", - "interpreter.functions.font.args.sizeHelpText": "字体大小(像素)", - "interpreter.functions.font.args.underlineHelpText": "为文本加下划线?", - "interpreter.functions.font.args.weightHelpText": "字体粗细。例如 {list} 或 {end}。", - "interpreter.functions.fontHelpText": "创建字体样式。", + "expressions_np.functions.kibana_context.help": "更新 kibana 全局上下文", + "expressions_np.functions.kibana.help": "获取 kibana 全局上下文", + "expressions_np.functions.font.args.alignHelpText": "水平文本对齐。", + "expressions_np.functions.font.args.colorHelpText": "文本颜色。", + "expressions_np.functions.font.args.italicHelpText": "使文本变为斜体?", + "expressions_np.functions.font.args.lHeightHelpText": "行高(像素)", + "expressions_np.functions.font.args.sizeHelpText": "字体大小(像素)", + "expressions_np.functions.font.args.underlineHelpText": "为文本加下划线?", + "expressions_np.functions.font.args.weightHelpText": "字体粗细。例如 {list} 或 {end}。", + "expressions_np.functions.fontHelpText": "创建字体样式。", "kbn.advancedSettings.context.defaultSizeText": "要在上下文视图中显示的周围条目数目", "kbn.advancedSettings.context.defaultSizeTitle": "上下文大小", "kbn.advancedSettings.context.sizeStepText": "递增或递减上下文大小的步进大小", @@ -3255,9 +3254,6 @@ "xpack.apm.invalidLicense.licenseManagementLink": "管理您的许可", "xpack.apm.invalidLicense.message": "APM UI 不可用,因为您当前的许可已过期或不再有效。", "xpack.apm.invalidLicense.title": "许可无效", - "xpack.apm.kueryBar.indexPatternMissingWarningMessage": "没有标题为 “{apmIndexPatternTitle}” 的可用 APM 索引模式。要使用查询栏,请选择通过{setupInstructionsLink}导入 APM 索引模式。", - "xpack.apm.kueryBar.searchPlaceholder": "搜索事务和错误……(例如 {queryExample}", - "xpack.apm.kueryBar.setupInstructionsLinkLabel": "设置说明", "xpack.apm.metadataTable.section.agentLabel": "代理", "xpack.apm.metadataTable.section.containerLabel": "容器", "xpack.apm.metadataTable.section.customLabel": "定制", @@ -5068,11 +5064,7 @@ "xpack.infra.logs.analysisPage.unavailable.mLDisabledTitle": "分析功能需要 Machine Learning", "xpack.infra.logs.highlights.goToNextHighlightButtonLabel": "跳转到下一高亮条目", "xpack.infra.logs.highlights.goToPreviousHighlightButtonLabel": "跳转到上一高亮条目", - "xpack.infra.logs.index.analysisBetaBadgeLabel": "公测版", "xpack.infra.logs.index.analysisBetaBadgeTitle": "分析", - "xpack.infra.logs.index.analysisBetaBadgeTooltipContent": "此功能仍处于开发状态。额外功能即将推出,某些功能可能会有变更。", - "xpack.infra.logs.index.analysisTabTitle": "分析", - "xpack.infra.logs.index.documentTitle": "Logs", "xpack.infra.logs.index.settingsTabTitle": "设置", "xpack.infra.logs.index.streamTabTitle": "流式传输", "xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent": "请注意,我们的 Machine Learning 机器人若干分钟后才会开始收集数据。", @@ -10478,4 +10470,4 @@ "xpack.fileUpload.fileParser.errorReadingFile": "读取文件时出错", "xpack.fileUpload.fileParser.noFileProvided": "错误,未提供任何文件" } -} \ No newline at end of file +} diff --git a/x-pack/test/functional/apps/uptime/overview.ts b/x-pack/test/functional/apps/uptime/overview.ts index 96d4aa10149ea..cdb904537a0f2 100644 --- a/x-pack/test/functional/apps/uptime/overview.ts +++ b/x-pack/test/functional/apps/uptime/overview.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects }: FtrProviderContext) => { @@ -23,11 +24,45 @@ export default ({ getPageObjects }: FtrProviderContext) => { it('runs filter query without issues', async () => { await pageObjects.uptime.inputFilterQuery( - DEFAULT_DATE_START, - DEFAULT_DATE_END, - 'monitor.status:up and monitor.id:"0000-intermittent"', - 'monitor-page-link-0000-intermittent' + 'monitor.status:up and monitor.id:"0000-intermittent"' ); + await pageObjects.uptime.pageHasExpectedIds(['0000-intermittent']); + }); + + it('pagination is cleared when filter criteria changes', async () => { + await pageObjects.uptime.goToUptimePageAndSetDateRange(DEFAULT_DATE_START, DEFAULT_DATE_END); + await pageObjects.uptime.changePage('next'); + // there should now be pagination data in the URL + const contains = await pageObjects.uptime.pageUrlContains('pagination'); + expect(contains).to.be(true); + await pageObjects.uptime.pageHasExpectedIds([ + '0010-down', + '0011-up', + '0012-up', + '0013-up', + '0014-up', + '0015-intermittent', + '0016-up', + '0017-up', + '0018-up', + '0019-up', + ]); + await pageObjects.uptime.setStatusFilter('up'); + // ensure that pagination is removed from the URL + const doesNotContain = await pageObjects.uptime.pageUrlContains('pagination'); + expect(doesNotContain).to.be(false); + await pageObjects.uptime.pageHasExpectedIds([ + '0000-intermittent', + '0001-up', + '0002-up', + '0003-up', + '0004-up', + '0005-up', + '0006-up', + '0007-up', + '0008-up', + '0009-up', + ]); }); }); }; diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index 4b413a9737756..52a454eeeb743 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -20,7 +20,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields" : "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"@message\",\"subType\":\"multi\"},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"@tags\",\"subType\":\"multi\"},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"agent\",\"subType\":\"multi\"},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"extension\",\"subType\":\"multi\"},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"headings\",\"subType\":\"multi\"},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"host\",\"subType\":\"multi\"},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"index\",\"subType\":\"multi\"},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"links\",\"subType\":\"multi\"},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"machine.os\",\"subType\":\"multi\"},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.article:section\",\"subType\":\"multi\"},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.article:tag\",\"subType\":\"multi\"},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.og:description\",\"subType\":\"multi\"},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.og:image\",\"subType\":\"multi\"},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.og:image:height\",\"subType\":\"multi\"},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.og:image:width\",\"subType\":\"multi\"},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.og:site_name\",\"subType\":\"multi\"},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.og:title\",\"subType\":\"multi\"},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.og:type\",\"subType\":\"multi\"},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.og:url\",\"subType\":\"multi\"},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.twitter:card\",\"subType\":\"multi\"},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.twitter:description\",\"subType\":\"multi\"},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.twitter:image\",\"subType\":\"multi\"},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.twitter:site\",\"subType\":\"multi\"},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.twitter:title\",\"subType\":\"multi\"},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"relatedContent.url\",\"subType\":\"multi\"},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"request\",\"subType\":\"multi\"},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"response\",\"subType\":\"multi\"},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"spaces\",\"subType\":\"multi\"},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"url\",\"subType\":\"multi\"},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"xss\",\"subType\":\"multi\"},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['@timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "fields" : "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"xss\"}}},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['@timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, diff --git a/x-pack/test/functional/es_archives/security/flstest/kibana/data.json b/x-pack/test/functional/es_archives/security/flstest/kibana/data.json index c54e8a9b44e2e..ad52bd61c2ea5 100644 --- a/x-pack/test/functional/es_archives/security/flstest/kibana/data.json +++ b/x-pack/test/functional/es_archives/security/flstest/kibana/data.json @@ -6,7 +6,7 @@ "source": { "index-pattern" : { "title" : "flstest", - "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"customer_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_name\",\"subType\":\"multi\"},{\"name\":\"customer_region\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_region.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_region\",\"subType\":\"multi\"},{\"name\":\"customer_ssn\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_ssn.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"parent\":\"customer_ssn\",\"subType\":\"multi\"}]" + "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"customer_name\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_name.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_name\"}}},{\"name\":\"customer_region\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_region.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_region\"}}},{\"name\":\"customer_ssn\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"customer_ssn.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"customer_ssn\"}}}]" }, "type" : "index-pattern", "references" : [ ], diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index 229f8ee55d442..26c95c3bf526d 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -11,6 +11,14 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo const uptimeService = getService('uptime'); return new (class UptimePage { + public async goToUptimePageAndSetDateRange( + datePickerStartValue: string, + datePickerEndValue: string + ) { + await pageObjects.common.navigateToApp('uptime'); + await pageObjects.timePicker.setAbsoluteRange(datePickerStartValue, datePickerEndValue); + } + public async goToUptimeOverviewAndLoadData( datePickerStartValue: string, datePickerEndValue: string, @@ -35,16 +43,32 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo } } - public async inputFilterQuery( - datePickerStartValue: string, - datePickerEndValue: string, - filterQuery: string, - testId: string - ) { - await pageObjects.common.navigateToApp('uptime'); - await pageObjects.timePicker.setAbsoluteRange(datePickerStartValue, datePickerEndValue); + public async inputFilterQuery(filterQuery: string) { await uptimeService.setFilterText(filterQuery); - await uptimeService.monitorIdExists(testId); + } + + public async pageHasExpectedIds(monitorIdsToCheck: string[]) { + await Promise.all(monitorIdsToCheck.map(id => uptimeService.monitorPageLinkExists(id))); + } + + public async pageUrlContains(value: string) { + return await uptimeService.urlContains(value); + } + + public async changePage(direction: 'next' | 'prev') { + if (direction === 'next') { + await uptimeService.goToNextPage(); + } else if (direction === 'prev') { + await uptimeService.goToPreviousPage(); + } + } + + public async setStatusFilter(value: 'up' | 'down') { + if (value === 'up') { + await uptimeService.setStatusFilterUp(); + } else if (value === 'down') { + await uptimeService.setStatusFilterDown(); + } } })(); } diff --git a/x-pack/test/functional/services/uptime.ts b/x-pack/test/functional/services/uptime.ts index fd1a61da2c0f5..40d2e3dafc7f8 100644 --- a/x-pack/test/functional/services/uptime.ts +++ b/x-pack/test/functional/services/uptime.ts @@ -19,6 +19,13 @@ export function UptimeProvider({ getService }: FtrProviderContext) { async monitorIdExists(key: string) { await testSubjects.existOrFail(key); }, + async monitorPageLinkExists(monitorId: string) { + await testSubjects.existOrFail(`monitor-page-link-${monitorId}`); + }, + async urlContains(expected: string) { + const url = await browser.getCurrentUrl(); + return url.indexOf(expected) >= 0; + }, async navigateToMonitorWithId(monitorId: string) { await testSubjects.click(`monitor-page-link-${monitorId}`); }, @@ -30,5 +37,17 @@ export function UptimeProvider({ getService }: FtrProviderContext) { await testSubjects.setValue('xpack.uptime.filterBar', filterQuery); await browser.pressKeys(browser.keys.ENTER); }, + async goToNextPage() { + await testSubjects.click('xpack.uptime.monitorList.nextButton'); + }, + async goToPreviousPage() { + await testSubjects.click('xpack.uptime.monitorList.prevButton'); + }, + async setStatusFilterUp() { + await testSubjects.click('xpack.uptime.filterBar.filterStatusUp'); + }, + async setStatusFilterDown() { + await testSubjects.click('xpack.uptime.filterBar.filterStatusDown'); + }, }; } diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index dd9a92c848fab..34361ad9df542 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -57,7 +57,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"@message\"},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"@tags\"},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"agent\"},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"extension\"},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"headings\"},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"host\"},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"index\"},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"links\"},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"machine.os\"},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.article:section\"},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.article:tag\"},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:description\"},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image\"},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image:height\"},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image:width\"},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:site_name\"},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:title\"},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:type\"},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:url\"},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:card\"},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:description\"},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:image\"},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:site\"},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:title\"},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.url\"},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"request\"},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"response\"},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"spaces\"},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"url\"},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"xss\"}]", + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"xss\"}}}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, @@ -148,7 +148,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"@message\"},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"@tags\"},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"agent\"},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"extension\"},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"headings\"},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"host\"},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"index\"},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"links\"},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"machine.os\"},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.article:section\"},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.article:tag\"},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:description\"},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image\"},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image:height\"},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image:width\"},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:site_name\"},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:title\"},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:type\"},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:url\"},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:card\"},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:description\"},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:image\"},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:site\"},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:title\"},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.url\"},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"request\"},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"response\"},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"spaces\"},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"url\"},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"xss\"}]", + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"xss\"}}}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, @@ -243,7 +243,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"@message\"},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"@tags\"},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"agent\"},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"extension\"},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"headings\"},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"host\"},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"index\"},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"links\"},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"machine.os\"},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.article:section\"},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.article:tag\"},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:description\"},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image\"},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image:height\"},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:image:width\"},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:site_name\"},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:title\"},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:type\"},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.og:url\"},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:card\"},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:description\"},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:image\"},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:site\"},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.twitter:title\"},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"relatedContent.url\"},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"request\"},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"response\"},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"spaces\"},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"url\"},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":\"multi\",\"parent\":\"xss\"}]", + "fields": "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"xss\"}}}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, diff --git a/x-pack/test/visual_regression/tests/infra/index.js b/x-pack/test/visual_regression/tests/infra/index.js index 9efd45e83a550..f3063746b37b5 100644 --- a/x-pack/test/visual_regression/tests/infra/index.js +++ b/x-pack/test/visual_regression/tests/infra/index.js @@ -7,7 +7,7 @@ export default function ({ loadTestFile, getService }) { const browser = getService('browser'); - describe('InfraUI Visual Regression', function () { + describe.skip('InfraUI Visual Regression', function () { before(async () => { await browser.setWindowSize(1600, 1000); }); diff --git a/x-pack/test/visual_regression/tests/login_page.js b/x-pack/test/visual_regression/tests/login_page.js index cfc53735dffcc..003a23086c7b6 100644 --- a/x-pack/test/visual_regression/tests/login_page.js +++ b/x-pack/test/visual_regression/tests/login_page.js @@ -11,8 +11,8 @@ export default function ({ getService, getPageObjects }) { const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'security']); - describe('Security', () => { - describe.skip('Login Page', () => { + describe.skip('Security', () => { + describe('Login Page', () => { before(async () => { await esArchiver.load('empty_kibana'); await PageObjects.security.logout();